diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-07-02 19:59:52 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-07-02 20:07:43 +0200 |
| commit | ebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch) | |
| tree | 2bbc24ddb4124c4bb14dec0e536129d4de37b056 /src | |
| parent | 3ab19185093d7709f824b95b979060ce125389d8 (diff) | |
Move everything into `crates/` directory
Diffstat (limited to 'src')
85 files changed, 0 insertions, 27794 deletions
diff --git a/src/diag.rs b/src/diag.rs deleted file mode 100644 index b5995be4..00000000 --- a/src/diag.rs +++ /dev/null @@ -1,376 +0,0 @@ -//! Diagnostics. - -use std::fmt::{self, Display, Formatter}; -use std::io; -use std::path::{Path, PathBuf}; -use std::str::Utf8Error; -use std::string::FromUtf8Error; - -use comemo::Tracked; - -use crate::file::PackageSpec; -use crate::syntax::{Span, Spanned}; -use crate::World; - -/// Early-return with a [`StrResult`] or [`SourceResult`]. -/// -/// If called with just a string and format args, returns with a -/// `StrResult`. If called with a span, a string and format args, returns -/// a `SourceResult`. -/// -/// ``` -/// bail!("bailing with a {}", "string result"); -/// bail!(span, "bailing with a {}", "source result"); -/// ``` -#[macro_export] -#[doc(hidden)] -macro_rules! __bail { - ($fmt:literal $(, $arg:expr)* $(,)?) => { - return Err($crate::diag::eco_format!($fmt, $($arg),*)) - }; - - ($error:expr) => { - return Err(Box::new(vec![$error])) - }; - - ($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => { - return Err(Box::new(vec![$crate::diag::SourceError::new( - $span, - $crate::diag::eco_format!($fmt, $($arg),*), - )])) - }; -} - -#[doc(inline)] -pub use crate::__bail as bail; - -/// Construct an [`EcoString`] or [`SourceError`]. -#[macro_export] -#[doc(hidden)] -macro_rules! __error { - ($fmt:literal $(, $arg:expr)* $(,)?) => { - $crate::diag::eco_format!($fmt, $($arg),*) - }; - - ($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => { - $crate::diag::SourceError::new( - $span, - $crate::diag::eco_format!($fmt, $($arg),*), - ) - }; -} - -#[doc(inline)] -pub use crate::__error as error; -#[doc(hidden)] -pub use ecow::{eco_format, EcoString}; - -/// A result that can carry multiple source errors. -pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>; - -/// An error in a source file. -/// -/// The contained spans will only be detached if any of the input source files -/// were detached. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct SourceError { - /// The span of the erroneous node in the source code. - pub span: Span, - /// A diagnostic message describing the problem. - pub message: EcoString, - /// The trace of function calls leading to the error. - pub trace: Vec<Spanned<Tracepoint>>, - /// Additonal hints to the user, indicating how this error could be avoided - /// or worked around. - pub hints: Vec<EcoString>, -} - -impl SourceError { - /// Create a new, bare error. - pub fn new(span: Span, message: impl Into<EcoString>) -> Self { - Self { - span, - trace: vec![], - message: message.into(), - hints: vec![], - } - } - - /// Adds user-facing hints to the error. - pub fn with_hints(mut self, hints: impl IntoIterator<Item = EcoString>) -> Self { - self.hints.extend(hints); - self - } -} - -/// A part of an error's [trace](SourceError::trace). -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum Tracepoint { - /// A function call. - Call(Option<EcoString>), - /// A show rule application. - Show(EcoString), - /// A module import. - Import, -} - -impl Display for Tracepoint { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Tracepoint::Call(Some(name)) => { - write!(f, "error occurred in this call of function `{}`", name) - } - Tracepoint::Call(None) => { - write!(f, "error occurred in this function call") - } - Tracepoint::Show(name) => { - write!(f, "error occurred while applying show rule to this {name}") - } - Tracepoint::Import => { - write!(f, "error occurred while importing this module") - } - } - } -} - -/// Enrich a [`SourceResult`] with a tracepoint. -pub trait Trace<T> { - /// Add the tracepoint to all errors that lie outside the `span`. - fn trace<F>(self, world: Tracked<dyn World + '_>, make_point: F, span: Span) -> Self - where - F: Fn() -> Tracepoint; -} - -impl<T> Trace<T> for SourceResult<T> { - fn trace<F>(self, world: Tracked<dyn World + '_>, make_point: F, span: Span) -> Self - where - F: Fn() -> Tracepoint, - { - self.map_err(|mut errors| { - if span.is_detached() { - return errors; - } - - let trace_range = span.range(&*world); - for error in errors.iter_mut().filter(|e| !e.span.is_detached()) { - // Skip traces that surround the error. - if error.span.id() == span.id() { - let error_range = error.span.range(&*world); - if trace_range.start <= error_range.start - && trace_range.end >= error_range.end - { - continue; - } - } - - error.trace.push(Spanned::new(make_point(), span)); - } - errors - }) - } -} - -/// A result type with a string error message. -pub type StrResult<T> = Result<T, EcoString>; - -/// Convert a [`StrResult`] to a [`SourceResult`] by adding span information. -pub trait At<T> { - /// Add the span information. - fn at(self, span: Span) -> SourceResult<T>; -} - -impl<T, S> At<T> for Result<T, S> -where - S: Into<EcoString>, -{ - fn at(self, span: Span) -> SourceResult<T> { - self.map_err(|message| Box::new(vec![SourceError::new(span, message)])) - } -} - -/// A result type with a string error message and hints. -pub type HintedStrResult<T> = Result<T, HintedString>; - -/// A string message with hints. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct HintedString { - /// A diagnostic message describing the problem. - pub message: EcoString, - /// Additonal hints to the user, indicating how this error could be avoided - /// or worked around. - pub hints: Vec<EcoString>, -} - -impl<T> At<T> for Result<T, HintedString> { - fn at(self, span: Span) -> SourceResult<T> { - self.map_err(|diags| { - Box::new(vec![SourceError::new(span, diags.message).with_hints(diags.hints)]) - }) - } -} - -/// Enrich a [`StrResult`] or [`HintedStrResult`] with a hint. -pub trait Hint<T> { - /// Add the hint. - fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T>; -} - -impl<T> Hint<T> for StrResult<T> { - fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> { - self.map_err(|message| HintedString { message, hints: vec![hint.into()] }) - } -} - -impl<T> Hint<T> for HintedStrResult<T> { - fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> { - self.map_err(|mut error| { - error.hints.push(hint.into()); - error - }) - } -} - -/// A result type with a file-related error. -pub type FileResult<T> = Result<T, FileError>; - -/// An error that occurred while trying to load of a file. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum FileError { - /// A file was not found at this path. - NotFound(PathBuf), - /// A file could not be accessed. - AccessDenied, - /// A directory was found, but a file was expected. - IsDirectory, - /// The file is not a Typst source file, but should have been. - NotSource, - /// The file was not valid UTF-8, but should have been. - InvalidUtf8, - /// The package the file is part of could not be loaded. - Package(PackageError), - /// Another error. - Other, -} - -impl FileError { - /// Create a file error from an I/O error. - pub fn from_io(error: io::Error, path: &Path) -> Self { - match error.kind() { - io::ErrorKind::NotFound => Self::NotFound(path.into()), - io::ErrorKind::PermissionDenied => Self::AccessDenied, - io::ErrorKind::InvalidData - if error.to_string().contains("stream did not contain valid UTF-8") => - { - Self::InvalidUtf8 - } - _ => Self::Other, - } - } -} - -impl std::error::Error for FileError {} - -impl Display for FileError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::NotFound(path) => { - write!(f, "file not found (searched at {})", path.display()) - } - Self::AccessDenied => f.pad("failed to load file (access denied)"), - Self::IsDirectory => f.pad("failed to load file (is a directory)"), - Self::NotSource => f.pad("not a typst source file"), - Self::InvalidUtf8 => f.pad("file is not valid utf-8"), - Self::Package(error) => error.fmt(f), - Self::Other => f.pad("failed to load file"), - } - } -} - -impl From<Utf8Error> for FileError { - fn from(_: Utf8Error) -> Self { - Self::InvalidUtf8 - } -} - -impl From<FromUtf8Error> for FileError { - fn from(_: FromUtf8Error) -> Self { - Self::InvalidUtf8 - } -} - -impl From<PackageError> for FileError { - fn from(error: PackageError) -> Self { - Self::Package(error) - } -} - -impl From<FileError> for EcoString { - fn from(error: FileError) -> Self { - eco_format!("{error}") - } -} - -/// A result type with a package-related error. -pub type PackageResult<T> = Result<T, PackageError>; - -/// An error that occured while trying to load a package. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum PackageError { - /// The specified package does not exist. - NotFound(PackageSpec), - /// Failed to retrieve the package through the network. - NetworkFailed, - /// The package archive was malformed. - MalformedArchive, - /// Another error. - Other, -} - -impl std::error::Error for PackageError {} - -impl Display for PackageError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::NotFound(spec) => { - write!(f, "package not found (searched for {spec})",) - } - Self::NetworkFailed => f.pad("failed to load package (network failed)"), - Self::MalformedArchive => f.pad("failed to load package (archive malformed)"), - Self::Other => f.pad("failed to load package"), - } - } -} - -impl From<PackageError> for EcoString { - fn from(error: PackageError) -> Self { - eco_format!("{error}") - } -} -/// Format a user-facing error message for an XML-like file format. -pub fn format_xml_like_error(format: &str, error: roxmltree::Error) -> EcoString { - match error { - roxmltree::Error::UnexpectedCloseTag { expected, actual, pos } => { - eco_format!( - "failed to parse {format}: found closing tag '{actual}' \ - instead of '{expected}' in line {}", - pos.row - ) - } - roxmltree::Error::UnknownEntityReference(entity, pos) => { - eco_format!( - "failed to parse {format}: unknown entity '{entity}' in line {}", - pos.row - ) - } - roxmltree::Error::DuplicatedAttribute(attr, pos) => { - eco_format!( - "failed to parse {format}: duplicate attribute '{attr}' in line {}", - pos.row - ) - } - roxmltree::Error::NoRootNode => { - eco_format!("failed to parse {format}: missing root node") - } - _ => eco_format!("failed to parse {format}"), - } -} diff --git a/src/doc.rs b/src/doc.rs deleted file mode 100644 index de16cece..00000000 --- a/src/doc.rs +++ /dev/null @@ -1,719 +0,0 @@ -//! Finished documents. - -use std::fmt::{self, Debug, Formatter}; -use std::num::NonZeroUsize; -use std::ops::Range; -use std::str::FromStr; -use std::sync::Arc; - -use ecow::EcoString; - -use crate::eval::{cast, dict, Dict, Value}; -use crate::font::Font; -use crate::geom::{ - self, rounded_rect, Abs, Align, Axes, Color, Corners, Dir, Em, Geometry, Length, - Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform, -}; -use crate::image::Image; -use crate::model::{Content, Location, MetaElem, StyleChain}; -use crate::syntax::Span; - -/// A finished document with metadata and page frames. -#[derive(Debug, Default, Clone, Hash)] -pub struct Document { - /// The page frames. - pub pages: Vec<Frame>, - /// The document's title. - pub title: Option<EcoString>, - /// The document's author. - pub author: Vec<EcoString>, -} - -/// A finished layout with items at fixed positions. -#[derive(Default, Clone, Hash)] -pub struct Frame { - /// The size of the frame. - size: Size, - /// The baseline of the frame measured from the top. If this is `None`, the - /// frame's implicit baseline is at the bottom. - baseline: Option<Abs>, - /// The items composing this layout. - items: Arc<Vec<(Point, FrameItem)>>, -} - -/// Constructor, accessors and setters. -impl Frame { - /// Create a new, empty frame. - /// - /// Panics the size is not finite. - #[track_caller] - pub fn new(size: Size) -> Self { - assert!(size.is_finite()); - Self { size, baseline: None, items: Arc::new(vec![]) } - } - - /// Whether the frame contains no items. - pub fn is_empty(&self) -> bool { - self.items.is_empty() - } - - /// The size of the frame. - pub fn size(&self) -> Size { - self.size - } - - /// The size of the frame, mutably. - pub fn size_mut(&mut self) -> &mut Size { - &mut self.size - } - - /// Set the size of the frame. - pub fn set_size(&mut self, size: Size) { - self.size = size; - } - - /// The width of the frame. - pub fn width(&self) -> Abs { - self.size.x - } - - /// The height of the frame. - pub fn height(&self) -> Abs { - self.size.y - } - - /// The vertical position of the frame's baseline. - pub fn baseline(&self) -> Abs { - self.baseline.unwrap_or(self.size.y) - } - - /// Whether the frame has a non-default baseline. - pub fn has_baseline(&self) -> bool { - self.baseline.is_some() - } - - /// Set the frame's baseline from the top. - pub fn set_baseline(&mut self, baseline: Abs) { - self.baseline = Some(baseline); - } - - /// The distance from the baseline to the top of the frame. - /// - /// This is the same as `baseline()`, but more in line with the terminology - /// used in math layout. - pub fn ascent(&self) -> Abs { - self.baseline() - } - - /// The distance from the baseline to the bottom of the frame. - pub fn descent(&self) -> Abs { - self.size.y - self.baseline() - } - - /// An iterator over the items inside this frame alongside their positions - /// relative to the top-left of the frame. - pub fn items(&self) -> std::slice::Iter<'_, (Point, FrameItem)> { - self.items.iter() - } -} - -/// Insert items and subframes. -impl Frame { - /// The layer the next item will be added on. This corresponds to the number - /// of items in the frame. - pub fn layer(&self) -> usize { - self.items.len() - } - - /// Add an item at a position in the foreground. - pub fn push(&mut self, pos: Point, item: FrameItem) { - Arc::make_mut(&mut self.items).push((pos, item)); - } - - /// Add a frame at a position in the foreground. - /// - /// Automatically decides whether to inline the frame or to include it as a - /// group based on the number of items in it. - pub fn push_frame(&mut self, pos: Point, frame: Frame) { - if self.should_inline(&frame) { - self.inline(self.layer(), pos, frame); - } else { - self.push(pos, FrameItem::Group(GroupItem::new(frame))); - } - } - - /// Insert an item at the given layer in the frame. - /// - /// This panics if the layer is greater than the number of layers present. - #[track_caller] - pub fn insert(&mut self, layer: usize, pos: Point, items: FrameItem) { - Arc::make_mut(&mut self.items).insert(layer, (pos, items)); - } - - /// Add an item at a position in the background. - pub fn prepend(&mut self, pos: Point, item: FrameItem) { - Arc::make_mut(&mut self.items).insert(0, (pos, item)); - } - - /// Add multiple items at a position in the background. - /// - /// The first item in the iterator will be the one that is most in the - /// background. - pub fn prepend_multiple<I>(&mut self, items: I) - where - I: IntoIterator<Item = (Point, FrameItem)>, - { - Arc::make_mut(&mut self.items).splice(0..0, items); - } - - /// Add a frame at a position in the background. - pub fn prepend_frame(&mut self, pos: Point, frame: Frame) { - if self.should_inline(&frame) { - self.inline(0, pos, frame); - } else { - self.prepend(pos, FrameItem::Group(GroupItem::new(frame))); - } - } - - /// Whether the given frame should be inlined. - fn should_inline(&self, frame: &Frame) -> bool { - self.items.is_empty() || frame.items.len() <= 5 - } - - /// Inline a frame at the given layer. - fn inline(&mut self, layer: usize, pos: Point, frame: Frame) { - // Try to just reuse the items. - if pos.is_zero() && self.items.is_empty() { - self.items = frame.items; - return; - } - - // Try to transfer the items without adjusting the position. - // Also try to reuse the items if the Arc isn't shared. - let range = layer..layer; - if pos.is_zero() { - let sink = Arc::make_mut(&mut self.items); - match Arc::try_unwrap(frame.items) { - Ok(items) => { - sink.splice(range, items); - } - Err(arc) => { - sink.splice(range, arc.iter().cloned()); - } - } - return; - } - - // We have to adjust the item positions. - // But still try to reuse the items if the Arc isn't shared. - let sink = Arc::make_mut(&mut self.items); - match Arc::try_unwrap(frame.items) { - Ok(items) => { - sink.splice(range, items.into_iter().map(|(p, e)| (p + pos, e))); - } - Err(arc) => { - sink.splice(range, arc.iter().cloned().map(|(p, e)| (p + pos, e))); - } - } - } -} - -/// Modify the frame. -impl Frame { - /// Remove all items from the frame. - pub fn clear(&mut self) { - if Arc::strong_count(&self.items) == 1 { - Arc::make_mut(&mut self.items).clear(); - } else { - self.items = Arc::new(vec![]); - } - } - - /// Resize the frame to a new size, distributing new space according to the - /// given alignments. - pub fn resize(&mut self, target: Size, aligns: Axes<Align>) { - if self.size != target { - let offset = Point::new( - aligns.x.position(target.x - self.size.x), - aligns.y.position(target.y - self.size.y), - ); - self.size = target; - self.translate(offset); - } - } - - /// Move the baseline and contents of the frame by an offset. - pub fn translate(&mut self, offset: Point) { - if !offset.is_zero() { - if let Some(baseline) = &mut self.baseline { - *baseline += offset.y; - } - for (point, _) in Arc::make_mut(&mut self.items) { - *point += offset; - } - } - } - - /// Attach the metadata from this style chain to the frame. - pub fn meta(&mut self, styles: StyleChain, force: bool) { - if force || !self.is_empty() { - self.meta_iter(MetaElem::data_in(styles)); - } - } - - /// Attach metadata from an iterator. - pub fn meta_iter(&mut self, iter: impl IntoIterator<Item = Meta>) { - let mut hide = false; - for meta in iter { - if matches!(meta, Meta::Hide) { - hide = true; - } else { - self.prepend(Point::zero(), FrameItem::Meta(meta, self.size)); - } - } - if hide { - Arc::make_mut(&mut self.items).retain(|(_, item)| { - matches!(item, FrameItem::Group(_) | FrameItem::Meta(Meta::Elem(_), _)) - }); - } - } - - /// Add a background fill. - pub fn fill(&mut self, fill: Paint) { - self.prepend( - Point::zero(), - FrameItem::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()), - ); - } - - /// Add a fill and stroke with optional radius and outset to the frame. - pub fn fill_and_stroke( - &mut self, - fill: Option<Paint>, - stroke: Sides<Option<Stroke>>, - outset: Sides<Rel<Abs>>, - radius: Corners<Rel<Abs>>, - span: Span, - ) { - let outset = outset.relative_to(self.size()); - let size = self.size() + outset.sum_by_axis(); - let pos = Point::new(-outset.left, -outset.top); - let radius = radius.map(|side| side.relative_to(size.x.min(size.y) / 2.0)); - self.prepend_multiple( - rounded_rect(size, radius, fill, stroke) - .into_iter() - .map(|x| (pos, FrameItem::Shape(x, span))), - ) - } - - /// Arbitrarily transform the contents of the frame. - pub fn transform(&mut self, transform: Transform) { - if !self.is_empty() { - self.group(|g| g.transform = transform); - } - } - - /// Clip the contents of a frame to its size. - pub fn clip(&mut self) { - if !self.is_empty() { - self.group(|g| g.clips = true); - } - } - - /// Wrap the frame's contents in a group and modify that group with `f`. - fn group<F>(&mut self, f: F) - where - F: FnOnce(&mut GroupItem), - { - let mut wrapper = Frame::new(self.size); - wrapper.baseline = self.baseline; - let mut group = GroupItem::new(std::mem::take(self)); - f(&mut group); - wrapper.push(Point::zero(), FrameItem::Group(group)); - *self = wrapper; - } -} - -/// Tools for debugging. -impl Frame { - /// Add a full size aqua background and a red baseline for debugging. - pub fn debug(mut self) -> Self { - self.insert( - 0, - Point::zero(), - FrameItem::Shape( - Geometry::Rect(self.size) - .filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()), - Span::detached(), - ), - ); - self.insert( - 1, - Point::with_y(self.baseline()), - FrameItem::Shape( - Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { - paint: Color::RED.into(), - thickness: Abs::pt(1.0), - ..Stroke::default() - }), - Span::detached(), - ), - ); - self - } - - /// Add a green marker at a position for debugging. - pub fn mark_point(&mut self, pos: Point) { - let radius = Abs::pt(2.0); - self.push( - pos - Point::splat(radius), - FrameItem::Shape( - geom::ellipse(Size::splat(2.0 * radius), Some(Color::GREEN.into()), None), - Span::detached(), - ), - ); - } - - /// Add a green marker line at a position for debugging. - pub fn mark_line(&mut self, y: Abs) { - self.push( - Point::with_y(y), - FrameItem::Shape( - Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { - paint: Color::GREEN.into(), - thickness: Abs::pt(1.0), - ..Stroke::default() - }), - Span::detached(), - ), - ); - } -} - -impl Debug for Frame { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Frame ")?; - f.debug_list() - .entries(self.items.iter().map(|(_, item)| item)) - .finish() - } -} - -/// The building block frames are composed of. -#[derive(Clone, Hash)] -pub enum FrameItem { - /// A subframe with optional transformation and clipping. - Group(GroupItem), - /// A run of shaped text. - Text(TextItem), - /// A geometric shape with optional fill and stroke. - Shape(Shape, Span), - /// An image and its size. - Image(Image, Size, Span), - /// Meta information and the region it applies to. - Meta(Meta, Size), -} - -impl Debug for FrameItem { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Group(group) => group.fmt(f), - Self::Text(text) => write!(f, "{text:?}"), - Self::Shape(shape, _) => write!(f, "{shape:?}"), - Self::Image(image, _, _) => write!(f, "{image:?}"), - Self::Meta(meta, _) => write!(f, "{meta:?}"), - } - } -} - -/// A subframe with optional transformation and clipping. -#[derive(Clone, Hash)] -pub struct GroupItem { - /// The group's frame. - pub frame: Frame, - /// A transformation to apply to the group. - pub transform: Transform, - /// Whether the frame should be a clipping boundary. - pub clips: bool, -} - -impl GroupItem { - /// Create a new group with default settings. - pub fn new(frame: Frame) -> Self { - Self { - frame, - transform: Transform::identity(), - clips: false, - } - } -} - -impl Debug for GroupItem { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Group ")?; - self.frame.fmt(f) - } -} - -/// A run of shaped text. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct TextItem { - /// The font the glyphs are contained in. - pub font: Font, - /// The font size. - pub size: Abs, - /// Glyph color. - pub fill: Paint, - /// The natural language of the text. - pub lang: Lang, - /// The item's plain text. - pub text: EcoString, - /// The glyphs. - pub glyphs: Vec<Glyph>, -} - -impl TextItem { - /// The width of the text run. - pub fn width(&self) -> Abs { - self.glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(self.size) - } -} - -impl Debug for TextItem { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Text(")?; - self.text.fmt(f)?; - f.write_str(")") - } -} - -/// A glyph in a run of shaped text. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Glyph { - /// The glyph's index in the font. - pub id: u16, - /// The advance width of the glyph. - pub x_advance: Em, - /// The horizontal offset of the glyph. - pub x_offset: Em, - /// The range of the glyph in its item's text. - pub range: Range<u16>, - /// The source code location of the text. - pub span: (Span, u16), -} - -impl Glyph { - /// The range of the glyph in its item's text. - pub fn range(&self) -> Range<usize> { - usize::from(self.range.start)..usize::from(self.range.end) - } -} - -/// An identifier for a natural language. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Lang([u8; 3], u8); - -impl Lang { - pub const ALBANIAN: Self = Self(*b"sq ", 2); - pub const ARABIC: Self = Self(*b"ar ", 2); - pub const BOKMÅL: Self = Self(*b"nb ", 2); - pub const CHINESE: Self = Self(*b"zh ", 2); - pub const CZECH: Self = Self(*b"cs ", 2); - pub const DANISH: Self = Self(*b"da ", 2); - pub const DUTCH: Self = Self(*b"nl ", 2); - pub const ENGLISH: Self = Self(*b"en ", 2); - pub const FILIPINO: Self = Self(*b"tl ", 2); - pub const FRENCH: Self = Self(*b"fr ", 2); - pub const GERMAN: Self = Self(*b"de ", 2); - pub const ITALIAN: Self = Self(*b"it ", 2); - pub const JAPANESE: Self = Self(*b"ja ", 2); - pub const NYNORSK: Self = Self(*b"nn ", 2); - pub const POLISH: Self = Self(*b"pl ", 2); - pub const PORTUGUESE: Self = Self(*b"pt ", 2); - pub const RUSSIAN: Self = Self(*b"ru ", 2); - pub const SLOVENIAN: Self = Self(*b"sl ", 2); - pub const SPANISH: Self = Self(*b"es ", 2); - pub const SWEDISH: Self = Self(*b"sv ", 2); - pub const TURKISH: Self = Self(*b"tr ", 2); - pub const UKRAINIAN: Self = Self(*b"ua ", 2); - pub const VIETNAMESE: Self = Self(*b"vi ", 2); - - /// Return the language code as an all lowercase string slice. - pub fn as_str(&self) -> &str { - std::str::from_utf8(&self.0[..usize::from(self.1)]).unwrap_or_default() - } - - /// The default direction for the language. - pub fn dir(self) -> Dir { - match self.as_str() { - "ar" | "dv" | "fa" | "he" | "ks" | "pa" | "ps" | "sd" | "ug" | "ur" - | "yi" => Dir::RTL, - _ => Dir::LTR, - } - } -} - -impl FromStr for Lang { - type Err = &'static str; - - /// Construct a language from a two- or three-byte ISO 639-1/2/3 code. - fn from_str(iso: &str) -> Result<Self, Self::Err> { - let len = iso.len(); - if matches!(len, 2..=3) && iso.is_ascii() { - let mut bytes = [b' '; 3]; - bytes[..len].copy_from_slice(iso.as_bytes()); - bytes.make_ascii_lowercase(); - Ok(Self(bytes, len as u8)) - } else { - Err("expected two or three letter language code (ISO 639-1/2/3)") - } - } -} - -cast! { - Lang, - self => self.as_str().into_value(), - string: EcoString => Self::from_str(&string)?, -} - -/// An identifier for a region somewhere in the world. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Region([u8; 2]); - -impl Region { - /// Return the region code as an all uppercase string slice. - pub fn as_str(&self) -> &str { - std::str::from_utf8(&self.0).unwrap_or_default() - } -} - -impl PartialEq<&str> for Region { - fn eq(&self, other: &&str) -> bool { - self.as_str() == *other - } -} - -impl FromStr for Region { - type Err = &'static str; - - /// Construct a region from its two-byte ISO 3166-1 alpha-2 code. - fn from_str(iso: &str) -> Result<Self, Self::Err> { - if iso.len() == 2 && iso.is_ascii() { - let mut bytes: [u8; 2] = iso.as_bytes().try_into().unwrap(); - bytes.make_ascii_uppercase(); - Ok(Self(bytes)) - } else { - Err("expected two letter region code (ISO 3166-1 alpha-2)") - } - } -} - -cast! { - Region, - self => self.as_str().into_value(), - string: EcoString => Self::from_str(&string)?, -} - -/// Meta information that isn't visible or renderable. -#[derive(Clone, PartialEq, Hash)] -pub enum Meta { - /// An internal or external link to a destination. - Link(Destination), - /// An identifiable element that produces something within the area this - /// metadata is attached to. - Elem(Content), - /// The numbering of the current page. - PageNumbering(Value), - /// Indicates that content should be hidden. This variant doesn't appear - /// in the final frames as it is removed alongside the content that should - /// be hidden. - Hide, -} - -cast! { - type Meta: "meta", -} - -impl Debug for Meta { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Link(dest) => write!(f, "Link({dest:?})"), - Self::Elem(content) => write!(f, "Elem({:?})", content.func()), - Self::PageNumbering(value) => write!(f, "PageNumbering({value:?})"), - Self::Hide => f.pad("Hide"), - } - } -} - -/// A link destination. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Destination { - /// A link to a URL. - Url(EcoString), - /// A link to a point on a page. - Position(Position), - /// An unresolved link to a location in the document. - Location(Location), -} - -cast! { - Destination, - self => match self { - Self::Url(v) => v.into_value(), - Self::Position(v) => v.into_value(), - Self::Location(v) => v.into_value(), - }, - v: EcoString => Self::Url(v), - v: Position => Self::Position(v), - v: Location => Self::Location(v), -} - -/// A physical position in a document. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Position { - /// The page, starting at 1. - pub page: NonZeroUsize, - /// The exact coordinates on the page (from the top left, as usual). - pub point: Point, -} - -cast! { - Position, - self => Value::Dict(self.into()), - mut dict: Dict => { - let page = dict.take("page")?.cast()?; - let x: Length = dict.take("x")?.cast()?; - let y: Length = dict.take("y")?.cast()?; - dict.finish(&["page", "x", "y"])?; - Self { page, point: Point::new(x.abs, y.abs) } - }, -} - -impl From<Position> for Dict { - fn from(pos: Position) -> Self { - dict! { - "page" => pos.page, - "x" => pos.point.x, - "y" => pos.point.y, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::option_eq; - - #[test] - fn test_region_option_eq() { - let region = Some(Region([b'U', b'S'])); - assert!(option_eq(region, "US")); - assert!(!option_eq(region, "AB")); - } - - #[test] - fn test_document_is_send() { - fn ensure_send<T: Send>() {} - ensure_send::<Document>(); - } -} diff --git a/src/eval/args.rs b/src/eval/args.rs deleted file mode 100644 index da29eeaf..00000000 --- a/src/eval/args.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; - -use ecow::{eco_format, EcoVec}; - -use super::{Array, Dict, FromValue, IntoValue, Str, Value}; -use crate::diag::{bail, At, SourceResult}; -use crate::syntax::{Span, Spanned}; -use crate::util::pretty_array_like; - -/// Evaluated arguments to a function. -#[derive(Clone, PartialEq, Hash)] -pub struct Args { - /// The span of the whole argument list. - pub span: Span, - /// The positional and named arguments. - pub items: EcoVec<Arg>, -} - -/// An argument to a function call: `12` or `draw: false`. -#[derive(Clone, PartialEq, Hash)] -pub struct Arg { - /// The span of the whole argument. - pub span: Span, - /// The name of the argument (`None` for positional arguments). - pub name: Option<Str>, - /// The value of the argument. - pub value: Spanned<Value>, -} - -impl Args { - /// Create positional arguments from a span and values. - pub fn new<T: IntoValue>(span: Span, values: impl IntoIterator<Item = T>) -> Self { - let items = values - .into_iter() - .map(|value| Arg { - span, - name: None, - value: Spanned::new(value.into_value(), span), - }) - .collect(); - Self { span, items } - } - - /// Push a positional argument. - pub fn push(&mut self, span: Span, value: Value) { - self.items.push(Arg { - span: self.span, - name: None, - value: Spanned::new(value, span), - }) - } - - /// Consume and cast the first positional argument if there is one. - pub fn eat<T>(&mut self) -> SourceResult<Option<T>> - where - T: FromValue<Spanned<Value>>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() { - let value = self.items.remove(i).value; - let span = value.span; - return T::from_value(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Consume n positional arguments if possible. - pub fn consume(&mut self, n: usize) -> SourceResult<Vec<Arg>> { - let mut list = vec![]; - - let mut i = 0; - while i < self.items.len() && list.len() < n { - if self.items[i].name.is_none() { - list.push(self.items.remove(i)); - } else { - i += 1; - } - } - - if list.len() < n { - bail!(self.span, "not enough arguments"); - } - - Ok(list) - } - - /// Consume and cast the first positional argument. - /// - /// Returns a `missing argument: {what}` error if no positional argument is - /// left. - pub fn expect<T>(&mut self, what: &str) -> SourceResult<T> - where - T: FromValue<Spanned<Value>>, - { - match self.eat()? { - Some(v) => Ok(v), - None => bail!(self.span, "missing argument: {what}"), - } - } - - /// Find and consume the first castable positional argument. - pub fn find<T>(&mut self) -> SourceResult<Option<T>> - where - T: FromValue<Spanned<Value>>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() && T::castable(&slot.value.v) { - let value = self.items.remove(i).value; - let span = value.span; - return T::from_value(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Find and consume all castable positional arguments. - pub fn all<T>(&mut self) -> SourceResult<Vec<T>> - where - T: FromValue<Spanned<Value>>, - { - let mut list = vec![]; - while let Some(value) = self.find()? { - list.push(value); - } - Ok(list) - } - - /// Cast and remove the value for the given named argument, returning an - /// error if the conversion fails. - pub fn named<T>(&mut self, name: &str) -> SourceResult<Option<T>> - where - T: FromValue<Spanned<Value>>, - { - // We don't quit once we have a match because when multiple matches - // exist, we want to remove all of them and use the last one. - let mut i = 0; - let mut found = None; - while i < self.items.len() { - if self.items[i].name.as_deref() == Some(name) { - let value = self.items.remove(i).value; - let span = value.span; - found = Some(T::from_value(value).at(span)?); - } else { - i += 1; - } - } - Ok(found) - } - - /// Same as named, but with fallback to find. - pub fn named_or_find<T>(&mut self, name: &str) -> SourceResult<Option<T>> - where - T: FromValue<Spanned<Value>>, - { - match self.named(name)? { - Some(value) => Ok(Some(value)), - None => self.find(), - } - } - - /// Take out all arguments into a new instance. - pub fn take(&mut self) -> Self { - Self { - span: self.span, - items: std::mem::take(&mut self.items), - } - } - - /// Return an "unexpected argument" error if there is any remaining - /// argument. - pub fn finish(self) -> SourceResult<()> { - if let Some(arg) = self.items.first() { - match &arg.name { - Some(name) => bail!(arg.span, "unexpected argument: {name}"), - _ => bail!(arg.span, "unexpected argument"), - } - } - Ok(()) - } - - /// Extract the positional arguments as an array. - pub fn to_pos(&self) -> Array { - self.items - .iter() - .filter(|item| item.name.is_none()) - .map(|item| item.value.v.clone()) - .collect() - } - - /// Extract the named arguments as a dictionary. - pub fn to_named(&self) -> Dict { - self.items - .iter() - .filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone()))) - .collect() - } -} - -impl Debug for Args { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let pieces: Vec<_> = - self.items.iter().map(|arg| eco_format!("{arg:?}")).collect(); - f.write_str(&pretty_array_like(&pieces, false)) - } -} - -impl Debug for Arg { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Some(name) = &self.name { - f.write_str(name)?; - f.write_str(": ")?; - } - Debug::fmt(&self.value.v, f) - } -} diff --git a/src/eval/array.rs b/src/eval/array.rs deleted file mode 100644 index a7a1387b..00000000 --- a/src/eval/array.rs +++ /dev/null @@ -1,508 +0,0 @@ -use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter}; -use std::ops::{Add, AddAssign}; - -use ecow::{eco_format, EcoString, EcoVec}; - -use super::{ops, Args, CastInfo, FromValue, Func, IntoValue, Reflect, Value, Vm}; -use crate::diag::{At, SourceResult, StrResult}; -use crate::syntax::Span; -use crate::util::pretty_array_like; - -/// Create a new [`Array`] from values. -#[macro_export] -#[doc(hidden)] -macro_rules! __array { - ($value:expr; $count:expr) => { - $crate::eval::Array::from($crate::eval::eco_vec![ - $crate::eval::IntoValue::into_value($value); - $count - ]) - }; - - ($($value:expr),* $(,)?) => { - $crate::eval::Array::from($crate::eval::eco_vec![$( - $crate::eval::IntoValue::into_value($value) - ),*]) - }; -} - -#[doc(inline)] -pub use crate::__array as array; -use crate::eval::ops::{add, mul}; -#[doc(hidden)] -pub use ecow::eco_vec; - -/// A reference counted array with value semantics. -#[derive(Default, Clone, PartialEq, Hash)] -pub struct Array(EcoVec<Value>); - -impl Array { - /// Create a new, empty array. - pub fn new() -> Self { - Self::default() - } - - /// Return `true` if the length is 0. - pub fn is_empty(&self) -> bool { - self.0.len() == 0 - } - - /// The length of the array. - pub fn len(&self) -> usize { - self.0.len() - } - - /// The first value in the array. - pub fn first(&self) -> StrResult<&Value> { - self.0.first().ok_or_else(array_is_empty) - } - - /// Mutably borrow the first value in the array. - pub fn first_mut(&mut self) -> StrResult<&mut Value> { - self.0.make_mut().first_mut().ok_or_else(array_is_empty) - } - - /// The last value in the array. - pub fn last(&self) -> StrResult<&Value> { - self.0.last().ok_or_else(array_is_empty) - } - - /// Mutably borrow the last value in the array. - pub fn last_mut(&mut self) -> StrResult<&mut Value> { - self.0.make_mut().last_mut().ok_or_else(array_is_empty) - } - - /// Borrow the value at the given index. - pub fn at<'a>( - &'a self, - index: i64, - default: Option<&'a Value>, - ) -> StrResult<&'a Value> { - self.locate(index) - .and_then(|i| self.0.get(i)) - .or(default) - .ok_or_else(|| out_of_bounds_no_default(index, self.len())) - } - - /// Mutably borrow the value at the given index. - pub fn at_mut(&mut self, index: i64) -> StrResult<&mut Value> { - let len = self.len(); - self.locate(index) - .and_then(move |i| self.0.make_mut().get_mut(i)) - .ok_or_else(|| out_of_bounds_no_default(index, len)) - } - - /// Push a value to the end of the array. - pub fn push(&mut self, value: Value) { - self.0.push(value); - } - - /// Remove the last value in the array. - pub fn pop(&mut self) -> StrResult<Value> { - self.0.pop().ok_or_else(array_is_empty) - } - - /// Insert a value at the specified index. - pub fn insert(&mut self, index: i64, value: Value) -> StrResult<()> { - let len = self.len(); - let i = self - .locate(index) - .filter(|&i| i <= self.0.len()) - .ok_or_else(|| out_of_bounds(index, len))?; - - self.0.insert(i, value); - Ok(()) - } - - /// Remove and return the value at the specified index. - pub fn remove(&mut self, index: i64) -> StrResult<Value> { - let len = self.len(); - let i = self - .locate(index) - .filter(|&i| i < self.0.len()) - .ok_or_else(|| out_of_bounds(index, len))?; - - Ok(self.0.remove(i)) - } - - /// Extract a contiguous subregion of the array. - pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { - let len = self.len(); - let start = self - .locate(start) - .filter(|&start| start <= self.0.len()) - .ok_or_else(|| out_of_bounds(start, len))?; - - let end = end.unwrap_or(self.len() as i64); - let end = self - .locate(end) - .filter(|&end| end <= self.0.len()) - .ok_or_else(|| out_of_bounds(end, len))? - .max(start); - - Ok(self.0[start..end].into()) - } - - /// Whether the array contains a specific value. - pub fn contains(&self, value: &Value) -> bool { - self.0.contains(value) - } - - /// Return the first matching item. - pub fn find(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<Value>> { - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(Some(item.clone())); - } - } - Ok(None) - } - - /// Return the index of the first matching item. - pub fn position(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<i64>> { - for (i, item) in self.iter().enumerate() { - let args = Args::new(func.span(), [item.clone()]); - if func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(Some(i as i64)); - } - } - - Ok(None) - } - - /// Return a new array with only those items for which the function returns - /// true. - pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { - let mut kept = EcoVec::new(); - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - kept.push(item.clone()) - } - } - Ok(kept.into()) - } - - /// Transform each item in the array with a function. - pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { - self.iter() - .map(|item| { - let args = Args::new(func.span(), [item.clone()]); - func.call_vm(vm, args) - }) - .collect() - } - - /// Fold all of the array's items into one with a function. - pub fn fold(&self, vm: &mut Vm, init: Value, func: Func) -> SourceResult<Value> { - let mut acc = init; - for item in self.iter() { - let args = Args::new(func.span(), [acc, item.clone()]); - acc = func.call_vm(vm, args)?; - } - Ok(acc) - } - - /// Calculates the sum of the array's items - pub fn sum(&self, default: Option<Value>, span: Span) -> SourceResult<Value> { - let mut acc = self - .first() - .map(|x| x.clone()) - .or_else(|_| { - default.ok_or_else(|| { - eco_format!("cannot calculate sum of empty array with no default") - }) - }) - .at(span)?; - for i in self.iter().skip(1) { - acc = add(acc, i.clone()).at(span)?; - } - Ok(acc) - } - - /// Calculates the product of the array's items - pub fn product(&self, default: Option<Value>, span: Span) -> SourceResult<Value> { - let mut acc = self - .first() - .map(|x| x.clone()) - .or_else(|_| { - default.ok_or_else(|| { - eco_format!("cannot calculate product of empty array with no default") - }) - }) - .at(span)?; - for i in self.iter().skip(1) { - acc = mul(acc, i.clone()).at(span)?; - } - Ok(acc) - } - - /// Whether any item matches. - pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(true); - } - } - - Ok(false) - } - - /// Whether all items match. - pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if !func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(false); - } - } - - Ok(true) - } - - /// Return a new array with all items from this and nested arrays. - pub fn flatten(&self) -> Self { - let mut flat = EcoVec::with_capacity(self.0.len()); - for item in self.iter() { - if let Value::Array(nested) = item { - flat.extend(nested.flatten().into_iter()); - } else { - flat.push(item.clone()); - } - } - flat.into() - } - - /// Returns a new array with reversed order. - pub fn rev(&self) -> Self { - 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 - /// separator (between the final two items). - pub fn join(&self, sep: Option<Value>, mut last: Option<Value>) -> StrResult<Value> { - let len = self.0.len(); - let sep = sep.unwrap_or(Value::None); - - let mut result = Value::None; - for (i, value) in self.iter().cloned().enumerate() { - if i > 0 { - if i + 1 == len && last.is_some() { - result = ops::join(result, last.take().unwrap())?; - } else { - result = ops::join(result, sep.clone())?; - } - } - - result = ops::join(result, value)?; - } - - Ok(result) - } - - /// Zips the array with another array. If the two arrays are of unequal length, it will only - /// zip up until the last element of the smaller array and the remaining elements will be - /// ignored. The return value is an array where each element is yet another array of size 2. - pub fn zip(&self, other: Array) -> Array { - self.iter() - .zip(other) - .map(|(first, second)| array![first.clone(), second].into_value()) - .collect() - } - - /// Return a sorted version of this array, optionally by a given key function. - /// - /// Returns an error if two values could not be compared or if the key function (if given) - /// yields an error. - pub fn sorted( - &self, - vm: &mut Vm, - span: Span, - key: Option<Func>, - ) -> SourceResult<Self> { - let mut result = Ok(()); - let mut vec = self.0.clone(); - let mut key_of = |x: Value| match &key { - // NOTE: We are relying on `comemo`'s memoization of function - // evaluation to not excessively reevaluate the `key`. - Some(f) => f.call_vm(vm, Args::new(f.span(), [x])), - None => Ok(x), - }; - vec.make_mut().sort_by(|a, b| { - // Until we get `try` blocks :) - match (key_of(a.clone()), key_of(b.clone())) { - (Ok(a), Ok(b)) => { - typst::eval::ops::compare(&a, &b).unwrap_or_else(|err| { - if result.is_ok() { - result = Err(err).at(span); - } - Ordering::Equal - }) - } - (Err(e), _) | (_, Err(e)) => { - if result.is_ok() { - result = Err(e); - } - Ordering::Equal - } - } - }); - result.map(|_| vec.into()) - } - - /// Repeat this array `n` times. - pub fn repeat(&self, n: i64) -> StrResult<Self> { - let count = usize::try_from(n) - .ok() - .and_then(|n| self.0.len().checked_mul(n)) - .ok_or_else(|| format!("cannot repeat this array {} times", n))?; - - Ok(self.iter().cloned().cycle().take(count).collect()) - } - - /// Extract a slice of the whole array. - pub fn as_slice(&self) -> &[Value] { - self.0.as_slice() - } - - /// Iterate over references to the contained values. - pub fn iter(&self) -> std::slice::Iter<Value> { - self.0.iter() - } - - /// Resolve an index. - fn locate(&self, index: i64) -> Option<usize> { - usize::try_from(if index >= 0 { - index - } else { - (self.len() as i64).checked_add(index)? - }) - .ok() - } - - /// Enumerate all items in the array. - pub fn enumerate(&self) -> Self { - self.iter() - .enumerate() - .map(|(i, value)| array![i, value.clone()].into_value()) - .collect() - } -} - -impl Debug for Array { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let pieces: Vec<_> = self.iter().map(|value| eco_format!("{value:?}")).collect(); - f.write_str(&pretty_array_like(&pieces, self.len() == 1)) - } -} - -impl Add for Array { - type Output = Self; - - fn add(mut self, rhs: Array) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Array { - fn add_assign(&mut self, rhs: Array) { - self.0.extend(rhs.0); - } -} - -impl Extend<Value> for Array { - fn extend<T: IntoIterator<Item = Value>>(&mut self, iter: T) { - self.0.extend(iter); - } -} - -impl FromIterator<Value> for Array { - fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self { - Self(iter.into_iter().collect()) - } -} - -impl IntoIterator for Array { - type Item = Value; - type IntoIter = ecow::vec::IntoIter<Value>; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a> IntoIterator for &'a Array { - type Item = &'a Value; - type IntoIter = std::slice::Iter<'a, Value>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl From<EcoVec<Value>> for Array { - fn from(v: EcoVec<Value>) -> Self { - Array(v) - } -} - -impl From<&[Value]> for Array { - fn from(v: &[Value]) -> Self { - Array(v.into()) - } -} - -impl<T> Reflect for Vec<T> { - fn describe() -> CastInfo { - Array::describe() - } - - fn castable(value: &Value) -> bool { - Array::castable(value) - } -} - -impl<T: IntoValue> IntoValue for Vec<T> { - fn into_value(self) -> Value { - Value::Array(self.into_iter().map(IntoValue::into_value).collect()) - } -} - -impl<T: FromValue> FromValue for Vec<T> { - fn from_value(value: Value) -> StrResult<Self> { - value.cast::<Array>()?.into_iter().map(Value::cast).collect() - } -} - -/// 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: usize) -> EcoString { - eco_format!("array index out of bounds (index: {index}, len: {len})") -} - -/// The out of bounds access error message when no default value was given. -#[cold] -fn out_of_bounds_no_default(index: i64, len: usize) -> EcoString { - eco_format!( - "array index out of bounds (index: {index}, len: {len}) \ - and no default value was specified", - ) -} diff --git a/src/eval/auto.rs b/src/eval/auto.rs deleted file mode 100644 index e73b3f33..00000000 --- a/src/eval/auto.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; - -use super::{CastInfo, FromValue, IntoValue, Reflect, Value}; -use crate::diag::StrResult; - -/// A value that indicates a smart default. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct AutoValue; - -impl IntoValue for AutoValue { - fn into_value(self) -> Value { - Value::Auto - } -} - -impl FromValue for AutoValue { - fn from_value(value: Value) -> StrResult<Self> { - match value { - Value::Auto => Ok(Self), - _ => Err(Self::error(&value)), - } - } -} - -impl Reflect for AutoValue { - fn describe() -> CastInfo { - CastInfo::Type("auto") - } - - fn castable(value: &Value) -> bool { - matches!(value, Value::Auto) - } -} - -impl Debug for AutoValue { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("auto") - } -} diff --git a/src/eval/cast.rs b/src/eval/cast.rs deleted file mode 100644 index 917972ed..00000000 --- a/src/eval/cast.rs +++ /dev/null @@ -1,316 +0,0 @@ -pub use typst_macros::{cast, Cast}; - -use std::fmt::Write; -use std::ops::Add; - -use ecow::EcoString; - -use super::Value; -use crate::diag::{At, SourceResult, StrResult}; -use crate::syntax::{Span, Spanned}; -use crate::util::separated_list; - -/// Determine details of a type. -/// -/// Type casting works as follows: -/// - [`Reflect for T`](Reflect) describes the possible Typst values for `T` -/// (for documentation and autocomplete). -/// - [`IntoValue for T`](IntoValue) is for conversion from `T -> Value` -/// (infallible) -/// - [`FromValue for T`](FromValue) is for conversion from `Value -> T` -/// (fallible). -/// -/// We can't use `TryFrom<Value>` due to conflicting impls. We could use -/// `From<T> for Value`, but that inverses the impl and leads to tons of -/// `.into()` all over the place that become hard to decipher. -pub trait Reflect { - /// Describe the acceptable values for this type. - fn describe() -> CastInfo; - - /// Whether the given value can be converted to `T`. - /// - /// This exists for performance. The check could also be done through the - /// [`CastInfo`], but it would be much more expensive (heap allocation + - /// dynamic checks instead of optimized machine code for each type). - fn castable(value: &Value) -> bool; - - /// Produce an error message for an inacceptable value. - /// - /// ``` - /// # use typst::eval::{Int, Reflect, Value}; - /// assert_eq!( - /// <Int as Reflect>::error(Value::None), - /// "expected integer, found none", - /// ); - /// ``` - fn error(found: &Value) -> EcoString { - Self::describe().error(found) - } -} - -impl Reflect for Value { - fn describe() -> CastInfo { - CastInfo::Any - } - - fn castable(_: &Value) -> bool { - true - } -} - -impl<T: Reflect> Reflect for Spanned<T> { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -impl<T: Reflect> Reflect for StrResult<T> { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -impl<T: Reflect> Reflect for SourceResult<T> { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -impl<T: Reflect> Reflect for &T { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -impl<T: Reflect> Reflect for &mut T { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -/// Cast a Rust type into a Typst [`Value`]. -/// -/// See also: [`Reflect`]. -pub trait IntoValue { - /// Cast this type into a value. - fn into_value(self) -> Value; -} - -impl IntoValue for Value { - fn into_value(self) -> Value { - self - } -} - -impl<T: IntoValue> IntoValue for Spanned<T> { - fn into_value(self) -> Value { - self.v.into_value() - } -} - -/// Cast a Rust type or result into a [`SourceResult<Value>`]. -/// -/// Converts `T`, [`StrResult<T>`], or [`SourceResult<T>`] into -/// [`SourceResult<Value>`] by `Ok`-wrapping or adding span information. -pub trait IntoResult { - /// Cast this type into a value. - fn into_result(self, span: Span) -> SourceResult<Value>; -} - -impl<T: IntoValue> IntoResult for T { - fn into_result(self, _: Span) -> SourceResult<Value> { - Ok(self.into_value()) - } -} - -impl<T: IntoValue> IntoResult for StrResult<T> { - fn into_result(self, span: Span) -> SourceResult<Value> { - self.map(IntoValue::into_value).at(span) - } -} - -impl<T: IntoValue> IntoResult for SourceResult<T> { - fn into_result(self, _: Span) -> SourceResult<Value> { - self.map(IntoValue::into_value) - } -} - -/// Try to cast a Typst [`Value`] into a Rust type. -/// -/// See also: [`Reflect`]. -pub trait FromValue<V = Value>: Sized + Reflect { - /// Try to cast the value into an instance of `Self`. - fn from_value(value: V) -> StrResult<Self>; -} - -impl FromValue for Value { - fn from_value(value: Value) -> StrResult<Self> { - Ok(value) - } -} - -impl<T: FromValue> FromValue<Spanned<Value>> for T { - fn from_value(value: Spanned<Value>) -> StrResult<Self> { - T::from_value(value.v) - } -} - -impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> { - fn from_value(value: Spanned<Value>) -> StrResult<Self> { - let span = value.span; - T::from_value(value.v).map(|t| Spanned::new(t, span)) - } -} - -/// Describes a possible value for a cast. -#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] -pub enum CastInfo { - /// Any value is okay. - Any, - /// A specific value, plus short documentation for that value. - Value(Value, &'static str), - /// Any value of a type. - Type(&'static str), - /// Multiple alternatives. - Union(Vec<Self>), -} - -impl CastInfo { - /// Produce an error message describing what was expected and what was - /// found. - pub fn error(&self, found: &Value) -> EcoString { - fn accumulate( - info: &CastInfo, - found: &Value, - parts: &mut Vec<EcoString>, - matching_type: &mut bool, - ) { - match info { - CastInfo::Any => parts.push("anything".into()), - CastInfo::Value(value, _) => { - parts.push(value.repr().into()); - if value.type_name() == found.type_name() { - *matching_type = true; - } - } - CastInfo::Type(ty) => parts.push((*ty).into()), - CastInfo::Union(options) => { - for option in options { - accumulate(option, found, parts, matching_type); - } - } - } - } - - let mut matching_type = false; - let mut parts = vec![]; - accumulate(self, found, &mut parts, &mut matching_type); - - let mut msg = String::from("expected "); - if parts.is_empty() { - msg.push_str(" nothing"); - } - - msg.push_str(&separated_list(&parts, "or")); - - if !matching_type { - msg.push_str(", found "); - msg.push_str(found.type_name()); - } - if_chain::if_chain! { - if let Value::Int(i) = found; - if parts.iter().any(|p| p == "length"); - if !matching_type; - then { - write!(msg, ": a length needs a unit - did you mean {i}pt?").unwrap(); - } - }; - - msg.into() - } -} - -impl Add for CastInfo { - type Output = Self; - - fn add(self, rhs: Self) -> Self { - Self::Union(match (self, rhs) { - (Self::Union(mut lhs), Self::Union(rhs)) => { - for cast in rhs { - if !lhs.contains(&cast) { - lhs.push(cast); - } - } - lhs - } - (Self::Union(mut lhs), rhs) => { - if !lhs.contains(&rhs) { - lhs.push(rhs); - } - lhs - } - (lhs, Self::Union(mut rhs)) => { - if !rhs.contains(&lhs) { - rhs.insert(0, lhs); - } - rhs - } - (lhs, rhs) => vec![lhs, rhs], - }) - } -} - -/// A container for a variadic argument. -pub trait Variadics { - /// The contained type. - type Inner; -} - -impl<T> Variadics for Vec<T> { - type Inner = T; -} - -/// An uninhabitable type. -pub enum Never {} - -impl Reflect for Never { - fn describe() -> CastInfo { - CastInfo::Union(vec![]) - } - - fn castable(_: &Value) -> bool { - false - } -} - -impl IntoValue for Never { - fn into_value(self) -> Value { - match self {} - } -} - -impl FromValue for Never { - fn from_value(value: Value) -> StrResult<Self> { - Err(Self::error(&value)) - } -} diff --git a/src/eval/datetime.rs b/src/eval/datetime.rs deleted file mode 100644 index f3c4a5a1..00000000 --- a/src/eval/datetime.rs +++ /dev/null @@ -1,201 +0,0 @@ -use std::fmt; -use std::fmt::{Debug, Formatter}; -use std::hash::Hash; - -use ecow::{eco_format, EcoString, EcoVec}; -use time::error::{Format, InvalidFormatDescription}; -use time::{format_description, PrimitiveDateTime}; - -use crate::eval::cast; -use crate::util::pretty_array_like; - -/// A datetime object that represents either a date, a time or a combination of -/// both. -#[derive(Clone, Copy, PartialEq, Hash)] -pub enum Datetime { - /// Representation as a date. - Date(time::Date), - /// Representation as a time. - Time(time::Time), - /// Representation as a combination of date and time. - Datetime(time::PrimitiveDateTime), -} - -impl Datetime { - /// Display the date and/or time in a certain format. - pub fn display(&self, pattern: Option<EcoString>) -> Result<EcoString, EcoString> { - let pattern = pattern.as_ref().map(EcoString::as_str).unwrap_or(match self { - Datetime::Date(_) => "[year]-[month]-[day]", - Datetime::Time(_) => "[hour]:[minute]:[second]", - Datetime::Datetime(_) => "[year]-[month]-[day] [hour]:[minute]:[second]", - }); - - let format = format_description::parse(pattern) - .map_err(format_time_invalid_format_description_error)?; - - let formatted_result = match self { - Datetime::Date(date) => date.format(&format), - Datetime::Time(time) => time.format(&format), - Datetime::Datetime(datetime) => datetime.format(&format), - } - .map(EcoString::from); - - formatted_result.map_err(format_time_format_error) - } - - /// Return the year of the datetime, if existing. - pub fn year(&self) -> Option<i32> { - match self { - Datetime::Date(date) => Some(date.year()), - Datetime::Time(_) => None, - Datetime::Datetime(datetime) => Some(datetime.year()), - } - } - - /// Return the month of the datetime, if existing. - pub fn month(&self) -> Option<u8> { - match self { - Datetime::Date(date) => Some(date.month().into()), - Datetime::Time(_) => None, - Datetime::Datetime(datetime) => Some(datetime.month().into()), - } - } - - /// Return the weekday of the datetime, if existing. - pub fn weekday(&self) -> Option<u8> { - match self { - Datetime::Date(date) => Some(date.weekday().number_from_monday()), - Datetime::Time(_) => None, - Datetime::Datetime(datetime) => Some(datetime.weekday().number_from_monday()), - } - } - - /// Return the day of the datetime, if existing. - pub fn day(&self) -> Option<u8> { - match self { - Datetime::Date(date) => Some(date.day()), - Datetime::Time(_) => None, - Datetime::Datetime(datetime) => Some(datetime.day()), - } - } - - /// Return the hour of the datetime, if existing. - pub fn hour(&self) -> Option<u8> { - match self { - Datetime::Date(_) => None, - Datetime::Time(time) => Some(time.hour()), - Datetime::Datetime(datetime) => Some(datetime.hour()), - } - } - - /// Return the minute of the datetime, if existing. - pub fn minute(&self) -> Option<u8> { - match self { - Datetime::Date(_) => None, - Datetime::Time(time) => Some(time.minute()), - Datetime::Datetime(datetime) => Some(datetime.minute()), - } - } - - /// Return the second of the datetime, if existing. - pub fn second(&self) -> Option<u8> { - match self { - Datetime::Date(_) => None, - Datetime::Time(time) => Some(time.second()), - Datetime::Datetime(datetime) => Some(datetime.second()), - } - } - - /// Create a datetime from year, month, and day. - pub fn from_ymd(year: i32, month: u8, day: u8) -> Option<Self> { - Some(Datetime::Date( - time::Date::from_calendar_date(year, time::Month::try_from(month).ok()?, day) - .ok()?, - )) - } - - /// Create a datetime from hour, minute, and second. - pub fn from_hms(hour: u8, minute: u8, second: u8) -> Option<Self> { - Some(Datetime::Time(time::Time::from_hms(hour, minute, second).ok()?)) - } - - /// Create a datetime from day and time. - pub fn from_ymd_hms( - year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, - ) -> Option<Self> { - let date = - time::Date::from_calendar_date(year, time::Month::try_from(month).ok()?, day) - .ok()?; - let time = time::Time::from_hms(hour, minute, second).ok()?; - Some(Datetime::Datetime(PrimitiveDateTime::new(date, time))) - } -} - -impl Debug for Datetime { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let year = self.year().map(|y| eco_format!("year: {y}")); - let month = self.month().map(|m| eco_format!("month: {m}")); - let day = self.day().map(|d| eco_format!("day: {d}")); - let hour = self.hour().map(|h| eco_format!("hour: {h}")); - let minute = self.minute().map(|m| eco_format!("minute: {m}")); - let second = self.second().map(|s| eco_format!("second: {s}")); - let filtered = [year, month, day, hour, minute, second] - .into_iter() - .flatten() - .collect::<EcoVec<_>>(); - - write!(f, "datetime{}", &pretty_array_like(&filtered, false)) - } -} - -cast! { - type Datetime: "datetime", -} - -/// Format the `Format` error of the time crate in an appropriate way. -fn format_time_format_error(error: Format) -> EcoString { - match error { - Format::InvalidComponent(name) => eco_format!("invalid component '{}'", name), - _ => "failed to format datetime in the requested format".into(), - } -} - -/// Format the `InvalidFormatDescription` error of the time crate in an -/// appropriate way. -fn format_time_invalid_format_description_error( - error: InvalidFormatDescription, -) -> EcoString { - match error { - InvalidFormatDescription::UnclosedOpeningBracket { index, .. } => { - eco_format!("missing closing bracket for bracket at index {}", index) - } - InvalidFormatDescription::InvalidComponentName { name, index, .. } => { - eco_format!("invalid component name '{}' at index {}", name, index) - } - InvalidFormatDescription::InvalidModifier { value, index, .. } => { - eco_format!("invalid modifier '{}' at index {}", value, index) - } - InvalidFormatDescription::Expected { what, index, .. } => { - eco_format!("expected {} at index {}", what, index) - } - InvalidFormatDescription::MissingComponentName { index, .. } => { - eco_format!("expected component name at index {}", index) - } - InvalidFormatDescription::MissingRequiredModifier { name, index, .. } => { - eco_format!( - "missing required modifier {} for component at index {}", - name, - index - ) - } - InvalidFormatDescription::NotSupported { context, what, index, .. } => { - eco_format!("{} is not supported in {} at index {}", what, context, index) - } - _ => "failed to parse datetime format".into(), - } -} diff --git a/src/eval/dict.rs b/src/eval/dict.rs deleted file mode 100644 index 3e6233ae..00000000 --- a/src/eval/dict.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::ops::{Add, AddAssign}; -use std::sync::Arc; - -use ecow::{eco_format, EcoString}; - -use super::{array, Array, Str, Value}; -use crate::diag::StrResult; -use crate::syntax::is_ident; -use crate::util::{pretty_array_like, separated_list, ArcExt}; - -/// Create a new [`Dict`] from key-value pairs. -#[macro_export] -#[doc(hidden)] -macro_rules! __dict { - ($($key:expr => $value:expr),* $(,)?) => {{ - #[allow(unused_mut)] - let mut map = $crate::eval::IndexMap::new(); - $(map.insert($key.into(), $crate::eval::IntoValue::into_value($value));)* - $crate::eval::Dict::from(map) - }}; -} - -#[doc(inline)] -pub use crate::__dict as dict; - -#[doc(inline)] -pub use indexmap::IndexMap; - -/// A reference-counted dictionary with value semantics. -#[derive(Default, Clone, PartialEq)] -pub struct Dict(Arc<IndexMap<Str, Value>>); - -impl Dict { - /// Create a new, empty dictionary. - pub fn new() -> Self { - Self::default() - } - - /// Whether the dictionary is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// The number of pairs in the dictionary. - pub fn len(&self) -> usize { - self.0.len() - } - - /// Borrow the value the given `key` maps to, - pub fn at<'a>( - &'a self, - key: &str, - default: Option<&'a Value>, - ) -> StrResult<&'a Value> { - self.0.get(key).or(default).ok_or_else(|| missing_key_no_default(key)) - } - - /// Mutably borrow the value the given `key` maps to. - pub fn at_mut(&mut self, key: &str) -> StrResult<&mut Value> { - Arc::make_mut(&mut self.0) - .get_mut(key) - .ok_or_else(|| missing_key_no_default(key)) - } - - /// Remove the value if the dictionary contains the given key. - pub fn take(&mut self, key: &str) -> StrResult<Value> { - Arc::make_mut(&mut self.0) - .remove(key) - .ok_or_else(|| eco_format!("missing key: {:?}", Str::from(key))) - } - - /// Whether the dictionary contains a specific key. - pub fn contains(&self, key: &str) -> bool { - self.0.contains_key(key) - } - - /// Insert a mapping from the given `key` to the given `value`. - pub fn insert(&mut self, key: Str, value: Value) { - Arc::make_mut(&mut self.0).insert(key, value); - } - - /// Remove a mapping by `key` and return the value. - pub fn remove(&mut self, key: &str) -> StrResult<Value> { - match Arc::make_mut(&mut self.0).shift_remove(key) { - Some(value) => Ok(value), - None => Err(missing_key(key)), - } - } - - /// Clear the dictionary. - pub fn clear(&mut self) { - if Arc::strong_count(&self.0) == 1 { - Arc::make_mut(&mut self.0).clear(); - } else { - *self = Self::new(); - } - } - - /// Return the keys of the dictionary as an array. - pub fn keys(&self) -> Array { - self.0.keys().cloned().map(Value::Str).collect() - } - - /// Return the values of the dictionary as an array. - pub fn values(&self) -> Array { - self.0.values().cloned().collect() - } - - /// Return the values of the dictionary as an array of pairs (arrays of - /// length two). - pub fn pairs(&self) -> Array { - self.0 - .iter() - .map(|(k, v)| Value::Array(array![k.clone(), v.clone()])) - .collect() - } - - /// Iterate over pairs of references to the contained keys and values. - pub fn iter(&self) -> indexmap::map::Iter<Str, Value> { - self.0.iter() - } - - /// Return an "unexpected key" error if there is any remaining pair. - pub fn finish(&self, expected: &[&str]) -> StrResult<()> { - if let Some((key, _)) = self.iter().next() { - let parts: Vec<_> = expected.iter().map(|s| eco_format!("\"{s}\"")).collect(); - let mut msg = format!("unexpected key {key:?}, valid keys are "); - msg.push_str(&separated_list(&parts, "and")); - return Err(msg.into()); - } - Ok(()) - } -} - -impl Debug for Dict { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if self.is_empty() { - return f.write_str("(:)"); - } - - let pieces: Vec<_> = self - .iter() - .map(|(key, value)| { - if is_ident(key) { - eco_format!("{key}: {value:?}") - } else { - eco_format!("{key:?}: {value:?}") - } - }) - .collect(); - - f.write_str(&pretty_array_like(&pieces, false)) - } -} - -impl Add for Dict { - type Output = Self; - - fn add(mut self, rhs: Dict) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Dict { - fn add_assign(&mut self, rhs: Dict) { - match Arc::try_unwrap(rhs.0) { - Ok(map) => self.extend(map), - Err(rc) => self.extend(rc.iter().map(|(k, v)| (k.clone(), v.clone()))), - } - } -} - -impl Hash for Dict { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_usize(self.0.len()); - for item in self { - item.hash(state); - } - } -} - -impl Extend<(Str, Value)> for Dict { - fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) { - Arc::make_mut(&mut self.0).extend(iter); - } -} - -impl FromIterator<(Str, Value)> for Dict { - fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self { - Self(Arc::new(iter.into_iter().collect())) - } -} - -impl IntoIterator for Dict { - type Item = (Str, Value); - type IntoIter = indexmap::map::IntoIter<Str, Value>; - - fn into_iter(self) -> Self::IntoIter { - Arc::take(self.0).into_iter() - } -} - -impl<'a> IntoIterator for &'a Dict { - type Item = (&'a Str, &'a Value); - type IntoIter = indexmap::map::Iter<'a, Str, Value>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl From<IndexMap<Str, Value>> for Dict { - fn from(map: IndexMap<Str, Value>) -> Self { - Self(Arc::new(map)) - } -} - -/// The missing key access error message. -#[cold] -fn missing_key(key: &str) -> EcoString { - eco_format!("dictionary does not contain key {:?}", Str::from(key)) -} - -/// The missing key access error message when no default was fiven. -#[cold] -fn missing_key_no_default(key: &str) -> EcoString { - eco_format!( - "dictionary does not contain key {:?} \ - and no default value was specified", - Str::from(key) - ) -} diff --git a/src/eval/func.rs b/src/eval/func.rs deleted file mode 100644 index 22f948ce..00000000 --- a/src/eval/func.rs +++ /dev/null @@ -1,643 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -use comemo::{Prehashed, Tracked, TrackedMut}; -use ecow::eco_format; -use once_cell::sync::Lazy; - -use super::{ - cast, Args, CastInfo, Eval, FlowEvent, IntoValue, Route, Scope, Scopes, Tracer, - Value, Vm, -}; -use crate::diag::{bail, SourceResult, StrResult}; -use crate::file::FileId; -use crate::model::{DelayedErrors, ElemFunc, Introspector, Locator, Vt}; -use crate::syntax::ast::{self, AstNode, Expr, Ident}; -use crate::syntax::{Span, SyntaxNode}; -use crate::World; - -/// An evaluatable function. -#[derive(Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Func { - /// The internal representation. - repr: Repr, - /// The span with which errors are reported when this function is called. - span: Span, -} - -/// The different kinds of function representations. -#[derive(Clone, PartialEq, Hash)] -enum Repr { - /// A native Rust function. - Native(&'static NativeFunc), - /// A function for an element. - Elem(ElemFunc), - /// A user-defined closure. - Closure(Arc<Prehashed<Closure>>), - /// A nested function with pre-applied arguments. - With(Arc<(Func, Args)>), -} - -impl Func { - /// The name of the function. - pub fn name(&self) -> Option<&str> { - match &self.repr { - Repr::Native(native) => Some(native.info.name), - Repr::Elem(func) => Some(func.info().name), - Repr::Closure(closure) => closure.name.as_deref(), - Repr::With(arc) => arc.0.name(), - } - } - - /// Extract details the function. - pub fn info(&self) -> Option<&FuncInfo> { - match &self.repr { - Repr::Native(native) => Some(&native.info), - Repr::Elem(func) => Some(func.info()), - Repr::Closure(_) => None, - Repr::With(arc) => arc.0.info(), - } - } - - /// The function's span. - pub fn span(&self) -> Span { - self.span - } - - /// Attach a span to this function if it doesn't already have one. - pub fn spanned(mut self, span: Span) -> Self { - if self.span.is_detached() { - self.span = span; - } - self - } - - /// Call the function with the given arguments. - pub fn call_vm(&self, vm: &mut Vm, mut args: Args) -> SourceResult<Value> { - let _span = tracing::info_span!( - "call", - name = self.name().unwrap_or("<anon>"), - file = 0, - ); - - match &self.repr { - Repr::Native(native) => { - let value = (native.func)(vm, &mut args)?; - args.finish()?; - Ok(value) - } - Repr::Elem(func) => { - let value = func.construct(vm, &mut args)?; - args.finish()?; - Ok(Value::Content(value)) - } - Repr::Closure(closure) => { - // Determine the route inside the closure. - let fresh = Route::new(closure.location); - let route = - if vm.location.is_detached() { fresh.track() } else { vm.route }; - - Closure::call( - self, - vm.world(), - route, - vm.vt.introspector, - vm.vt.locator.track(), - TrackedMut::reborrow_mut(&mut vm.vt.delayed), - TrackedMut::reborrow_mut(&mut vm.vt.tracer), - vm.depth + 1, - args, - ) - } - Repr::With(arc) => { - args.items = arc.1.items.iter().cloned().chain(args.items).collect(); - arc.0.call_vm(vm, args) - } - } - } - - /// Call the function with a Vt. - #[tracing::instrument(skip_all)] - pub fn call_vt<T: IntoValue>( - &self, - vt: &mut Vt, - args: impl IntoIterator<Item = T>, - ) -> SourceResult<Value> { - let route = Route::default(); - let scopes = Scopes::new(None); - let mut locator = Locator::chained(vt.locator.track()); - let vt = Vt { - world: vt.world, - introspector: vt.introspector, - locator: &mut locator, - delayed: TrackedMut::reborrow_mut(&mut vt.delayed), - tracer: TrackedMut::reborrow_mut(&mut vt.tracer), - }; - let mut vm = Vm::new(vt, route.track(), FileId::detached(), scopes); - let args = Args::new(self.span(), args); - self.call_vm(&mut vm, args) - } - - /// Apply the given arguments to the function. - pub fn with(self, args: Args) -> Self { - let span = self.span; - Self { repr: Repr::With(Arc::new((self, args))), span } - } - - /// Extract the element function, if it is one. - pub fn element(&self) -> Option<ElemFunc> { - match self.repr { - Repr::Elem(func) => Some(func), - _ => None, - } - } - - /// Get a field from this function's scope, if possible. - pub fn get(&self, field: &str) -> StrResult<&Value> { - match &self.repr { - Repr::Native(func) => func.info.scope.get(field).ok_or_else(|| { - eco_format!( - "function `{}` does not contain field `{}`", - func.info.name, - field - ) - }), - Repr::Elem(func) => func.info().scope.get(field).ok_or_else(|| { - eco_format!( - "function `{}` does not contain field `{}`", - func.name(), - field - ) - }), - Repr::Closure(_) => { - Err(eco_format!("cannot access fields on user-defined functions")) - } - Repr::With(arc) => arc.0.get(field), - } - } -} - -impl Debug for Func { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self.name() { - Some(name) => write!(f, "{name}"), - None => f.write_str("(..) => .."), - } - } -} - -impl PartialEq for Func { - fn eq(&self, other: &Self) -> bool { - self.repr == other.repr - } -} - -impl From<Repr> for Func { - fn from(repr: Repr) -> Self { - Self { repr, span: Span::detached() } - } -} - -impl From<ElemFunc> for Func { - fn from(func: ElemFunc) -> Self { - Repr::Elem(func).into() - } -} - -/// A Typst function defined by a native Rust function. -pub struct NativeFunc { - /// The function's implementation. - pub func: fn(&mut Vm, &mut Args) -> SourceResult<Value>, - /// Details about the function. - pub info: Lazy<FuncInfo>, -} - -impl PartialEq for NativeFunc { - fn eq(&self, other: &Self) -> bool { - self.func as usize == other.func as usize - } -} - -impl Eq for NativeFunc {} - -impl Hash for NativeFunc { - fn hash<H: Hasher>(&self, state: &mut H) { - (self.func as usize).hash(state); - } -} - -impl From<&'static NativeFunc> for Func { - fn from(native: &'static NativeFunc) -> Self { - Repr::Native(native).into() - } -} - -cast! { - &'static NativeFunc, - self => Value::Func(self.into()), -} - -/// Details about a function. -#[derive(Debug, Clone)] -pub struct FuncInfo { - /// The function's name. - pub name: &'static str, - /// The display name of the function. - pub display: &'static str, - /// A string of search keywords. - pub keywords: Option<&'static str>, - /// Which category the function is part of. - pub category: &'static str, - /// Documentation for the function. - pub docs: &'static str, - /// Details about the function's parameters. - pub params: Vec<ParamInfo>, - /// Valid values for the return value. - pub returns: CastInfo, - /// The function's own scope of fields and sub-functions. - pub scope: Scope, -} - -impl FuncInfo { - /// Get the parameter info for a parameter with the given name - pub fn param(&self, name: &str) -> Option<&ParamInfo> { - self.params.iter().find(|param| param.name == name) - } -} - -/// Describes a named parameter. -#[derive(Debug, Clone)] -pub struct ParamInfo { - /// The parameter's name. - pub name: &'static str, - /// Documentation for the parameter. - pub docs: &'static str, - /// Valid values for the parameter. - pub cast: CastInfo, - /// Creates an instance of the parameter's default value. - pub default: Option<fn() -> Value>, - /// Is the parameter positional? - pub positional: bool, - /// Is the parameter named? - /// - /// Can be true even if `positional` is true if the parameter can be given - /// in both variants. - pub named: bool, - /// Can the parameter be given any number of times? - pub variadic: bool, - /// Is the parameter required? - pub required: bool, - /// Is the parameter settable with a set rule? - pub settable: bool, -} - -/// A user-defined closure. -#[derive(Hash)] -pub(super) struct Closure { - /// The source file where the closure was defined. - pub location: FileId, - /// The name of the closure. - pub name: Option<Ident>, - /// Captured values from outer scopes. - pub captured: Scope, - /// The list of parameters. - pub params: Vec<Param>, - /// The expression the closure should evaluate to. - pub body: Expr, -} - -/// A closure parameter. -#[derive(Hash)] -pub enum Param { - /// A positional parameter: `x`. - Pos(ast::Pattern), - /// A named parameter with a default value: `draw: false`. - Named(Ident, Value), - /// An argument sink: `..args`. - Sink(Option<Ident>), -} - -impl Closure { - /// Call the function in the context with the arguments. - #[comemo::memoize] - #[tracing::instrument(skip_all)] - #[allow(clippy::too_many_arguments)] - fn call( - this: &Func, - world: Tracked<dyn World + '_>, - route: Tracked<Route>, - introspector: Tracked<Introspector>, - locator: Tracked<Locator>, - delayed: TrackedMut<DelayedErrors>, - tracer: TrackedMut<Tracer>, - depth: usize, - mut args: Args, - ) -> SourceResult<Value> { - let closure = match &this.repr { - Repr::Closure(closure) => closure, - _ => panic!("`this` must be a closure"), - }; - - // Don't leak the scopes from the call site. Instead, we use the scope - // of captured variables we collected earlier. - let mut scopes = Scopes::new(None); - scopes.top = closure.captured.clone(); - - // Prepare VT. - let mut locator = Locator::chained(locator); - let vt = Vt { - world, - introspector, - locator: &mut locator, - delayed, - tracer, - }; - - // Prepare VM. - let mut vm = Vm::new(vt, route, closure.location, scopes); - vm.depth = depth; - - // Provide the closure itself for recursive calls. - if let Some(name) = &closure.name { - vm.define(name.clone(), Value::Func(this.clone())); - } - - // Parse the arguments according to the parameter list. - let num_pos_params = - closure.params.iter().filter(|p| matches!(p, Param::Pos(_))).count(); - let num_pos_args = args.to_pos().len(); - let sink_size = num_pos_args.checked_sub(num_pos_params); - - let mut sink = None; - let mut sink_pos_values = None; - for p in &closure.params { - match p { - Param::Pos(pattern) => match pattern { - ast::Pattern::Normal(ast::Expr::Ident(ident)) => { - vm.define(ident.clone(), args.expect::<Value>(ident)?) - } - ast::Pattern::Normal(_) => unreachable!(), - _ => { - pattern.define( - &mut vm, - args.expect::<Value>("pattern parameter")?, - )?; - } - }, - Param::Sink(ident) => { - sink = ident.clone(); - if let Some(sink_size) = sink_size { - sink_pos_values = Some(args.consume(sink_size)?); - } - } - Param::Named(ident, default) => { - let value = - args.named::<Value>(ident)?.unwrap_or_else(|| default.clone()); - vm.define(ident.clone(), value); - } - } - } - - if let Some(sink) = sink { - let mut remaining_args = args.take(); - if let Some(sink_pos_values) = sink_pos_values { - remaining_args.items.extend(sink_pos_values); - } - vm.define(sink, remaining_args); - } - - // Ensure all arguments have been used. - args.finish()?; - - // Handle control flow. - let result = closure.body.eval(&mut vm); - match vm.flow { - Some(FlowEvent::Return(_, Some(explicit))) => return Ok(explicit), - Some(FlowEvent::Return(_, None)) => {} - Some(flow) => bail!(flow.forbidden()), - None => {} - } - - result - } -} - -impl From<Closure> for Func { - fn from(closure: Closure) -> Self { - Repr::Closure(Arc::new(Prehashed::new(closure))).into() - } -} - -cast! { - Closure, - self => Value::Func(self.into()), -} - -/// A visitor that determines which variables to capture for a closure. -pub(super) struct CapturesVisitor<'a> { - external: &'a Scopes<'a>, - internal: Scopes<'a>, - captures: Scope, -} - -impl<'a> CapturesVisitor<'a> { - /// Create a new visitor for the given external scopes. - pub fn new(external: &'a Scopes) -> Self { - Self { - external, - internal: Scopes::new(None), - captures: Scope::new(), - } - } - - /// Return the scope of captured variables. - pub fn finish(self) -> Scope { - self.captures - } - - /// Visit any node and collect all captured variables. - #[tracing::instrument(skip_all)] - pub fn visit(&mut self, node: &SyntaxNode) { - match node.cast() { - // Every identifier is a potential variable that we need to capture. - // Identifiers that shouldn't count as captures because they - // actually bind a new name are handled below (individually through - // the expressions that contain them). - Some(ast::Expr::Ident(ident)) => self.capture(ident), - Some(ast::Expr::MathIdent(ident)) => self.capture_in_math(ident), - - // Code and content blocks create a scope. - Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => { - self.internal.enter(); - for child in node.children() { - self.visit(child); - } - self.internal.exit(); - } - - // A closure contains parameter bindings, which are bound before the - // body is evaluated. Care must be taken so that the default values - // of named parameters cannot access previous parameter bindings. - Some(ast::Expr::Closure(expr)) => { - for param in expr.params().children() { - if let ast::Param::Named(named) = param { - self.visit(named.expr().as_untyped()); - } - } - - self.internal.enter(); - if let Some(name) = expr.name() { - self.bind(name); - } - - for param in expr.params().children() { - match param { - ast::Param::Pos(pattern) => { - for ident in pattern.idents() { - self.bind(ident); - } - } - ast::Param::Named(named) => self.bind(named.name()), - ast::Param::Sink(spread) => { - self.bind(spread.name().unwrap_or_default()) - } - } - } - - self.visit(expr.body().as_untyped()); - self.internal.exit(); - } - - // A let expression contains a binding, but that binding is only - // active after the body is evaluated. - Some(ast::Expr::Let(expr)) => { - if let Some(init) = expr.init() { - self.visit(init.as_untyped()); - } - - for ident in expr.kind().idents() { - self.bind(ident); - } - } - - // A for loop contains one or two bindings in its pattern. These are - // active after the iterable is evaluated but before the body is - // evaluated. - Some(ast::Expr::For(expr)) => { - self.visit(expr.iter().as_untyped()); - self.internal.enter(); - - let pattern = expr.pattern(); - for ident in pattern.idents() { - self.bind(ident); - } - - self.visit(expr.body().as_untyped()); - self.internal.exit(); - } - - // An import contains items, but these are active only after the - // path is evaluated. - Some(ast::Expr::Import(expr)) => { - self.visit(expr.source().as_untyped()); - if let Some(ast::Imports::Items(items)) = expr.imports() { - for item in items { - self.bind(item); - } - } - } - - // Everything else is traversed from left to right. - _ => { - for child in node.children() { - self.visit(child); - } - } - } - } - - /// Bind a new internal variable. - fn bind(&mut self, ident: ast::Ident) { - self.internal.top.define(ident.take(), Value::None); - } - - /// Capture a variable if it isn't internal. - fn capture(&mut self, ident: ast::Ident) { - if self.internal.get(&ident).is_err() { - if let Ok(value) = self.external.get(&ident) { - self.captures.define_captured(ident.take(), value.clone()); - } - } - } - - /// Capture a variable in math mode if it isn't internal. - fn capture_in_math(&mut self, ident: ast::MathIdent) { - if self.internal.get(&ident).is_err() { - if let Ok(value) = self.external.get_in_math(&ident) { - self.captures.define_captured(ident.take(), value.clone()); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::parse; - - #[track_caller] - fn test(text: &str, result: &[&str]) { - let mut scopes = Scopes::new(None); - scopes.top.define("f", 0); - scopes.top.define("x", 0); - scopes.top.define("y", 0); - scopes.top.define("z", 0); - - let mut visitor = CapturesVisitor::new(&scopes); - let root = parse(text); - visitor.visit(&root); - - let captures = visitor.finish(); - let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect(); - names.sort(); - - assert_eq!(names, result); - } - - #[test] - fn test_captures() { - // Let binding and function definition. - test("#let x = x", &["x"]); - test("#let x; #(x + y)", &["y"]); - test("#let f(x, y) = x + y", &[]); - test("#let f(x, y) = f", &[]); - test("#let f = (x, y) => f", &["f"]); - - // Closure with different kinds of params. - test("#((x, y) => x + z)", &["z"]); - test("#((x: y, z) => x + z)", &["y"]); - test("#((..x) => x + y)", &["y"]); - test("#((x, y: x + z) => x + y)", &["x", "z"]); - test("#{x => x; x}", &["x"]); - - // Show rule. - test("#show y: x => x", &["y"]); - test("#show y: x => x + z", &["y", "z"]); - test("#show x: x => x", &["x"]); - - // For loop. - test("#for x in y { x + z }", &["y", "z"]); - test("#for (x, y) in y { x + y }", &["y"]); - test("#for x in y {} #x", &["x", "y"]); - - // Import. - test("#import z: x, y", &["z"]); - test("#import x + y: x, y, z", &["x", "y"]); - - // Blocks. - test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]); - test("#[#let x = 1]#x", &["x"]); - } -} diff --git a/src/eval/int.rs b/src/eval/int.rs deleted file mode 100644 index 4e081617..00000000 --- a/src/eval/int.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::num::{NonZeroI64, NonZeroIsize, NonZeroU64, NonZeroUsize}; - -use super::{cast, Value}; - -macro_rules! signed_int { - ($($ty:ty)*) => { - $(cast! { - $ty, - self => Value::Int(self as i64), - v: i64 => v.try_into().map_err(|_| "number too large")?, - })* - } -} - -macro_rules! unsigned_int { - ($($ty:ty)*) => { - $(cast! { - $ty, - self => Value::Int(self as i64), - v: i64 => v.try_into().map_err(|_| { - if v < 0 { - "number must be at least zero" - } else { - "number too large" - } - })?, - })* - } -} - -macro_rules! signed_nonzero { - ($($ty:ty)*) => { - $(cast! { - $ty, - self => Value::Int(self.get() as i64), - v: i64 => v - .try_into() - .ok() - .and_then($ty::new) - .ok_or_else(|| if v == 0 { - "number must not be zero" - } else { - "number too large" - })?, - })* - } -} - -macro_rules! unsigned_nonzero { - ($($ty:ty)*) => { - $(cast! { - $ty, - self => Value::Int(self.get() as i64), - v: i64 => v - .try_into() - .ok() - .and_then($ty::new) - .ok_or_else(|| if v <= 0 { - "number must be positive" - } else { - "number too large" - })?, - })* - } -} - -signed_int! { - i8 i16 i32 isize -} - -unsigned_int! { - u8 u16 u32 u64 usize -} - -signed_nonzero! { - NonZeroI64 NonZeroIsize -} - -unsigned_nonzero! { - NonZeroU64 NonZeroUsize -} diff --git a/src/eval/library.rs b/src/eval/library.rs deleted file mode 100644 index 1b05de83..00000000 --- a/src/eval/library.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::num::NonZeroUsize; - -use comemo::Tracked; -use ecow::EcoString; -use std::sync::OnceLock; - -use super::{Args, Dynamic, Module, Value, Vm}; -use crate::diag::SourceResult; -use crate::doc::Document; -use crate::geom::{Abs, Dir}; -use crate::model::{Content, ElemFunc, Introspector, Label, StyleChain, Styles, Vt}; -use crate::syntax::Span; -use crate::util::hash128; - -/// Definition of Typst's standard library. -#[derive(Debug, Clone, Hash)] -pub struct Library { - /// The scope containing definitions that are available everywhere. - pub global: Module, - /// The scope containing definitions available in math mode. - pub math: Module, - /// The default properties for page size, font selection and so on. - pub styles: Styles, - /// Defines which standard library items fulfill which syntactical roles. - pub items: LangItems, -} - -/// Definition of library items the language is aware of. -#[derive(Clone)] -pub struct LangItems { - /// The root layout function. - pub layout: - fn(vt: &mut Vt, content: &Content, styles: StyleChain) -> SourceResult<Document>, - /// Access the em size. - pub em: fn(StyleChain) -> Abs, - /// Access the text direction. - pub dir: fn(StyleChain) -> Dir, - /// Whitespace. - pub space: fn() -> Content, - /// A forced line break: `\`. - pub linebreak: fn() -> Content, - /// Plain text without markup. - pub text: fn(text: EcoString) -> Content, - /// The text function. - pub text_func: ElemFunc, - /// Get the string if this is a text element. - pub text_str: fn(&Content) -> Option<EcoString>, - /// A smart quote: `'` or `"`. - pub smart_quote: fn(double: bool) -> Content, - /// A paragraph break. - pub parbreak: fn() -> Content, - /// Strong content: `*Strong*`. - pub strong: fn(body: Content) -> Content, - /// Emphasized content: `_Emphasized_`. - pub emph: fn(body: Content) -> Content, - /// Raw text with optional syntax highlighting: `` `...` ``. - pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content, - /// The language names and tags supported by raw text. - pub raw_languages: fn() -> Vec<(&'static str, Vec<&'static str>)>, - /// A hyperlink: `https://typst.org`. - pub link: fn(url: EcoString) -> Content, - /// A reference: `@target`, `@target[..]`. - pub reference: fn(target: Label, supplement: Option<Content>) -> Content, - /// The keys contained in the bibliography and short descriptions of them. - #[allow(clippy::type_complexity)] - pub bibliography_keys: - fn(introspector: Tracked<Introspector>) -> Vec<(EcoString, Option<EcoString>)>, - /// A section heading: `= Introduction`. - pub heading: fn(level: NonZeroUsize, body: Content) -> Content, - /// The heading function. - pub heading_func: ElemFunc, - /// An item in a bullet list: `- ...`. - pub list_item: fn(body: Content) -> Content, - /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. - pub enum_item: fn(number: Option<usize>, body: Content) -> Content, - /// An item in a term list: `/ Term: Details`. - pub term_item: fn(term: Content, description: Content) -> Content, - /// A mathematical equation: `$x$`, `$ x^2 $`. - pub equation: fn(body: Content, block: bool) -> Content, - /// An alignment point in math: `&`. - pub math_align_point: fn() -> Content, - /// Matched delimiters in math: `[x + y]`. - pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content, - /// A base with optional attachments in math: `a_1^2`. - #[allow(clippy::type_complexity)] - pub math_attach: fn( - base: Content, - // Positioned smartly. - t: Option<Content>, - b: Option<Content>, - // Fixed positions. - tl: Option<Content>, - bl: Option<Content>, - tr: Option<Content>, - br: Option<Content>, - ) -> Content, - /// A base with an accent: `arrow(x)`. - pub math_accent: fn(base: Content, accent: char) -> Content, - /// A fraction in math: `x/2`. - pub math_frac: fn(num: Content, denom: Content) -> Content, - /// A root in math: `√x`, `∛x` or `∜x`. - pub math_root: fn(index: Option<Content>, radicand: Content) -> Content, - /// Dispatch a method on a library value. - pub library_method: fn( - vm: &mut Vm, - dynamic: &Dynamic, - method: &str, - args: Args, - span: Span, - ) -> SourceResult<Value>, -} - -impl Debug for LangItems { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("LangItems { .. }") - } -} - -impl Hash for LangItems { - fn hash<H: Hasher>(&self, state: &mut H) { - (self.layout as usize).hash(state); - (self.em as usize).hash(state); - (self.dir as usize).hash(state); - self.space.hash(state); - self.linebreak.hash(state); - self.text.hash(state); - self.text_func.hash(state); - (self.text_str as usize).hash(state); - self.smart_quote.hash(state); - self.parbreak.hash(state); - self.strong.hash(state); - self.emph.hash(state); - self.raw.hash(state); - self.raw_languages.hash(state); - self.link.hash(state); - self.reference.hash(state); - (self.bibliography_keys as usize).hash(state); - self.heading.hash(state); - self.heading_func.hash(state); - self.list_item.hash(state); - self.enum_item.hash(state); - self.term_item.hash(state); - self.equation.hash(state); - self.math_align_point.hash(state); - self.math_delimited.hash(state); - self.math_attach.hash(state); - self.math_accent.hash(state); - self.math_frac.hash(state); - self.math_root.hash(state); - (self.library_method as usize).hash(state); - } -} - -/// Global storage for lang items. -#[doc(hidden)] -pub static LANG_ITEMS: OnceLock<LangItems> = OnceLock::new(); - -/// Set the lang items. -/// -/// This is a hack :( -/// -/// Passing the lang items everywhere they are needed (especially text related -/// things) is very painful. By storing them globally, in theory, we break -/// incremental, but only when different sets of lang items are used in the same -/// program. For this reason, if this function is called multiple times, the -/// items must be the same (and this is enforced). -pub fn set_lang_items(items: LangItems) { - if let Err(items) = LANG_ITEMS.set(items) { - let first = hash128(LANG_ITEMS.get().unwrap()); - let second = hash128(&items); - assert_eq!(first, second, "set differing lang items"); - } -} - -/// Access a lang item. -macro_rules! item { - ($name:ident) => { - $crate::eval::LANG_ITEMS.get().unwrap().$name - }; -} diff --git a/src/eval/methods.rs b/src/eval/methods.rs deleted file mode 100644 index 62ac4095..00000000 --- a/src/eval/methods.rs +++ /dev/null @@ -1,373 +0,0 @@ -//! Methods on values. - -use ecow::EcoString; - -use super::{Args, IntoValue, Str, Value, Vm}; -use crate::diag::{At, SourceResult}; -use crate::eval::Datetime; -use crate::model::{Location, Selector}; -use crate::syntax::Span; - -/// Call a method on a value. -pub fn call( - vm: &mut Vm, - value: Value, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult<Value> { - let name = value.type_name(); - let missing = || Err(missing_method(name, method)).at(span); - - let output = match value { - Value::Color(color) => match method { - "lighten" => color.lighten(args.expect("amount")?).into_value(), - "darken" => color.darken(args.expect("amount")?).into_value(), - "negate" => color.negate().into_value(), - _ => return missing(), - }, - - Value::Str(string) => match method { - "len" => string.len().into_value(), - "first" => string.first().at(span)?.into_value(), - "last" => string.last().at(span)?.into_value(), - "at" => { - let index = args.expect("index")?; - let default = args.named::<EcoString>("default")?; - string.at(index, default.as_deref()).at(span)?.into_value() - } - "slice" => { - let start = args.expect("start")?; - let mut end = args.eat()?; - if end.is_none() { - end = args.named("count")?.map(|c: i64| start + c); - } - string.slice(start, end).at(span)?.into_value() - } - "clusters" => string.clusters().into_value(), - "codepoints" => string.codepoints().into_value(), - "contains" => string.contains(args.expect("pattern")?).into_value(), - "starts-with" => string.starts_with(args.expect("pattern")?).into_value(), - "ends-with" => string.ends_with(args.expect("pattern")?).into_value(), - "find" => string.find(args.expect("pattern")?).into_value(), - "position" => string.position(args.expect("pattern")?).into_value(), - "match" => string.match_(args.expect("pattern")?).into_value(), - "matches" => string.matches(args.expect("pattern")?).into_value(), - "replace" => { - let pattern = args.expect("pattern")?; - let with = args.expect("string or function")?; - let count = args.named("count")?; - string.replace(vm, pattern, with, count)?.into_value() - } - "trim" => { - let pattern = args.eat()?; - let at = args.named("at")?; - let repeat = args.named("repeat")?.unwrap_or(true); - string.trim(pattern, at, repeat).into_value() - } - "split" => string.split(args.eat()?).into_value(), - _ => return missing(), - }, - - Value::Content(content) => match method { - "func" => content.func().into_value(), - "has" => content.has(&args.expect::<EcoString>("field")?).into_value(), - "at" => content - .at(&args.expect::<EcoString>("field")?, args.named("default")?) - .at(span)?, - "fields" => content.dict().into_value(), - "location" => content - .location() - .ok_or("this method can only be called on content returned by query(..)") - .at(span)? - .into_value(), - _ => return missing(), - }, - - Value::Array(array) => match method { - "len" => array.len().into_value(), - "first" => array.first().at(span)?.clone(), - "last" => array.last().at(span)?.clone(), - "at" => array - .at(args.expect("index")?, args.named("default")?.as_ref()) - .at(span)? - .clone(), - "slice" => { - let start = args.expect("start")?; - let mut end = args.eat()?; - if end.is_none() { - end = args.named("count")?.map(|c: i64| start + c); - } - array.slice(start, end).at(span)?.into_value() - } - "contains" => array.contains(&args.expect("value")?).into_value(), - "find" => array.find(vm, args.expect("function")?)?.into_value(), - "position" => array.position(vm, args.expect("function")?)?.into_value(), - "filter" => array.filter(vm, args.expect("function")?)?.into_value(), - "map" => array.map(vm, args.expect("function")?)?.into_value(), - "fold" => { - array.fold(vm, args.expect("initial value")?, args.expect("function")?)? - } - "sum" => array.sum(args.named("default")?, span)?, - "product" => array.product(args.named("default")?, span)?, - "any" => array.any(vm, args.expect("function")?)?.into_value(), - "all" => array.all(vm, args.expect("function")?)?.into_value(), - "flatten" => array.flatten().into_value(), - "rev" => array.rev().into_value(), - "split" => array.split(args.expect("separator")?).into_value(), - "join" => { - let sep = args.eat()?; - let last = args.named("last")?; - array.join(sep, last).at(span)? - } - "sorted" => array.sorted(vm, span, args.named("key")?)?.into_value(), - "zip" => array.zip(args.expect("other")?).into_value(), - "enumerate" => array.enumerate().into_value(), - _ => return missing(), - }, - - Value::Dict(dict) => match method { - "len" => dict.len().into_value(), - "at" => dict - .at(&args.expect::<Str>("key")?, args.named("default")?.as_ref()) - .at(span)? - .clone(), - "keys" => dict.keys().into_value(), - "values" => dict.values().into_value(), - "pairs" => dict.pairs().into_value(), - _ => return missing(), - }, - - Value::Func(func) => match method { - "with" => func.with(args.take()).into_value(), - "where" => { - let fields = args.to_named(); - args.items.retain(|arg| arg.name.is_none()); - func.element() - .ok_or("`where()` can only be called on element functions") - .at(span)? - .where_(fields) - .into_value() - } - _ => return missing(), - }, - - Value::Args(args) => match method { - "pos" => args.to_pos().into_value(), - "named" => args.to_named().into_value(), - _ => return missing(), - }, - - Value::Dyn(dynamic) => { - if let Some(location) = dynamic.downcast::<Location>() { - match method { - "page" => vm.vt.introspector.page(*location).into_value(), - "position" => vm.vt.introspector.position(*location).into_value(), - "page-numbering" => vm.vt.introspector.page_numbering(*location), - _ => return missing(), - } - } else if let Some(selector) = dynamic.downcast::<Selector>() { - match method { - "or" => selector.clone().or(args.all::<Selector>()?).into_value(), - "and" => selector.clone().and(args.all::<Selector>()?).into_value(), - "before" => { - let location = args.expect::<Selector>("selector")?; - let inclusive = - args.named_or_find::<bool>("inclusive")?.unwrap_or(true); - selector.clone().before(location, inclusive).into_value() - } - "after" => { - let location = args.expect::<Selector>("selector")?; - let inclusive = - args.named_or_find::<bool>("inclusive")?.unwrap_or(true); - selector.clone().after(location, inclusive).into_value() - } - _ => return missing(), - } - } else if let Some(&datetime) = dynamic.downcast::<Datetime>() { - match method { - "display" => { - datetime.display(args.eat()?).at(args.span)?.into_value() - } - "year" => datetime.year().into_value(), - "month" => datetime.month().into_value(), - "weekday" => datetime.weekday().into_value(), - "day" => datetime.day().into_value(), - "hour" => datetime.hour().into_value(), - "minute" => datetime.minute().into_value(), - "second" => datetime.second().into_value(), - _ => return missing(), - } - } else { - return (vm.items.library_method)(vm, &dynamic, method, args, span); - } - } - - _ => return missing(), - }; - - args.finish()?; - Ok(output) -} - -/// Call a mutating method on a value. -pub fn call_mut( - value: &mut Value, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult<Value> { - let name = value.type_name(); - let missing = || Err(missing_method(name, method)).at(span); - let mut output = Value::None; - - match value { - Value::Array(array) => match method { - "push" => array.push(args.expect("value")?), - "pop" => output = array.pop().at(span)?, - "insert" => { - array.insert(args.expect("index")?, args.expect("value")?).at(span)? - } - "remove" => output = array.remove(args.expect("index")?).at(span)?, - _ => return missing(), - }, - - Value::Dict(dict) => match method { - "insert" => dict.insert(args.expect::<Str>("key")?, args.expect("value")?), - "remove" => { - output = dict.remove(&args.expect::<EcoString>("key")?).at(span)? - } - _ => return missing(), - }, - - _ => return missing(), - } - - args.finish()?; - Ok(output) -} - -/// Call an accessor method on a value. -pub fn call_access<'a>( - value: &'a mut Value, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult<&'a mut Value> { - let name = value.type_name(); - let missing = || Err(missing_method(name, method)).at(span); - - let slot = match value { - Value::Array(array) => match method { - "first" => array.first_mut().at(span)?, - "last" => array.last_mut().at(span)?, - "at" => array.at_mut(args.expect("index")?).at(span)?, - _ => return missing(), - }, - Value::Dict(dict) => match method { - "at" => dict.at_mut(&args.expect::<Str>("key")?).at(span)?, - _ => return missing(), - }, - _ => return missing(), - }; - - args.finish()?; - Ok(slot) -} - -/// Whether a specific method is mutating. -pub fn is_mutating(method: &str) -> bool { - matches!(method, "push" | "pop" | "insert" | "remove") -} - -/// Whether a specific method is an accessor. -pub fn is_accessor(method: &str) -> bool { - matches!(method, "first" | "last" | "at") -} - -/// The missing method error message. -#[cold] -fn missing_method(type_name: &str, method: &str) -> String { - format!("type {type_name} has no method `{method}`") -} - -/// List the available methods for a type and whether they take arguments. -pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { - match type_name { - "color" => &[("lighten", true), ("darken", true), ("negate", false)], - "string" => &[ - ("len", false), - ("at", true), - ("clusters", false), - ("codepoints", false), - ("contains", true), - ("ends-with", true), - ("find", true), - ("first", false), - ("last", false), - ("match", true), - ("matches", true), - ("position", true), - ("replace", true), - ("slice", true), - ("split", true), - ("starts-with", true), - ("trim", true), - ], - "content" => &[ - ("func", false), - ("has", true), - ("at", true), - ("fields", false), - ("location", false), - ], - "array" => &[ - ("all", true), - ("any", true), - ("at", true), - ("contains", true), - ("filter", true), - ("find", true), - ("first", false), - ("flatten", false), - ("fold", true), - ("insert", true), - ("split", true), - ("join", true), - ("last", false), - ("len", false), - ("map", true), - ("pop", false), - ("position", true), - ("push", true), - ("remove", true), - ("rev", false), - ("slice", true), - ("sorted", false), - ("enumerate", false), - ("zip", true), - ], - "dictionary" => &[ - ("at", true), - ("insert", true), - ("keys", false), - ("len", false), - ("pairs", false), - ("remove", true), - ("values", false), - ], - "function" => &[("where", true), ("with", true)], - "arguments" => &[("named", false), ("pos", false)], - "location" => &[("page", false), ("position", false), ("page-numbering", false)], - "selector" => &[("or", true), ("and", true), ("before", true), ("after", true)], - "counter" => &[ - ("display", true), - ("at", true), - ("final", true), - ("step", true), - ("update", true), - ], - "state" => &[("display", true), ("at", true), ("final", true), ("update", true)], - _ => &[], - } -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs deleted file mode 100644 index fe28e3f3..00000000 --- a/src/eval/mod.rs +++ /dev/null @@ -1,1908 +0,0 @@ -//! Evaluation of markup into modules. - -#[macro_use] -mod library; -#[macro_use] -mod cast; -#[macro_use] -mod array; -#[macro_use] -mod dict; -#[macro_use] -mod str; -#[macro_use] -mod value; -mod args; -mod auto; -mod datetime; -mod func; -mod int; -mod methods; -mod module; -mod none; -pub mod ops; -mod scope; -mod symbol; - -#[doc(hidden)] -pub use { - self::library::LANG_ITEMS, - ecow::{eco_format, eco_vec}, - indexmap::IndexMap, - once_cell::sync::Lazy, -}; - -#[doc(inline)] -pub use typst_macros::{func, symbols}; - -pub use self::args::{Arg, Args}; -pub use self::array::{array, Array}; -pub use self::auto::AutoValue; -pub use self::cast::{ - cast, Cast, CastInfo, FromValue, IntoResult, IntoValue, Never, Reflect, Variadics, -}; -pub use self::datetime::Datetime; -pub use self::dict::{dict, Dict}; -pub use self::func::{Func, FuncInfo, NativeFunc, Param, ParamInfo}; -pub use self::library::{set_lang_items, LangItems, Library}; -pub use self::methods::methods_on; -pub use self::module::Module; -pub use self::none::NoneValue; -pub use self::scope::{Scope, Scopes}; -pub use self::str::{format_str, Regex, Str}; -pub use self::symbol::Symbol; -pub use self::value::{Dynamic, Type, Value}; - -use std::collections::HashSet; -use std::mem; -use std::path::Path; - -use comemo::{Track, Tracked, TrackedMut, Validate}; -use ecow::{EcoString, EcoVec}; -use unicode_segmentation::UnicodeSegmentation; - -use self::func::{CapturesVisitor, Closure}; -use crate::diag::{ - bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, -}; -use crate::file::{FileId, PackageManifest, PackageSpec}; -use crate::model::{ - Content, DelayedErrors, Introspector, Label, Locator, Recipe, ShowableSelector, - Styles, Transform, Unlabellable, Vt, -}; -use crate::syntax::ast::{self, AstNode}; -use crate::syntax::{parse_code, Source, Span, Spanned, SyntaxKind, SyntaxNode}; -use crate::World; - -const MAX_ITERATIONS: usize = 10_000; -const MAX_CALL_DEPTH: usize = 64; - -/// Evaluate a source file and return the resulting module. -#[comemo::memoize] -#[tracing::instrument(skip(world, route, tracer, source))] -pub fn eval( - world: Tracked<dyn World + '_>, - route: Tracked<Route>, - tracer: TrackedMut<Tracer>, - source: &Source, -) -> SourceResult<Module> { - // Prevent cyclic evaluation. - let id = source.id(); - if route.contains(id) { - panic!("Tried to cyclicly evaluate {}", id.path().display()); - } - - // Hook up the lang items. - let library = world.library(); - set_lang_items(library.items.clone()); - - // Prepare VT. - let mut locator = Locator::default(); - let introspector = Introspector::default(); - let mut delayed = DelayedErrors::default(); - let vt = Vt { - world, - introspector: introspector.track(), - locator: &mut locator, - delayed: delayed.track_mut(), - tracer, - }; - - // Prepare VM. - let route = Route::insert(route, id); - let scopes = Scopes::new(Some(library)); - let mut vm = Vm::new(vt, route.track(), id, scopes); - let root = match source.root().cast::<ast::Markup>() { - Some(markup) if vm.traced.is_some() => markup, - _ => source.ast()?, - }; - - // Evaluate the module. - let result = root.eval(&mut vm); - - // Handle control flow. - if let Some(flow) = vm.flow { - bail!(flow.forbidden()); - } - - // Assemble the module. - let name = id.path().file_stem().unwrap_or_default().to_string_lossy(); - Ok(Module::new(name).with_scope(vm.scopes.top).with_content(result?)) -} - -/// Evaluate a string as code and return the resulting value. -/// -/// Everything in the output is associated with the given `span`. -#[comemo::memoize] -pub fn eval_string( - world: Tracked<dyn World + '_>, - code: &str, - span: Span, -) -> SourceResult<Value> { - let mut root = parse_code(code); - root.synthesize(span); - - let errors = root.errors(); - if !errors.is_empty() { - return Err(Box::new(errors)); - } - - // Prepare VT. - let mut tracer = Tracer::default(); - let mut locator = Locator::default(); - let mut delayed = DelayedErrors::default(); - let introspector = Introspector::default(); - let vt = Vt { - world, - introspector: introspector.track(), - locator: &mut locator, - delayed: delayed.track_mut(), - tracer: tracer.track_mut(), - }; - - // Prepare VM. - let route = Route::default(); - let id = FileId::detached(); - let scopes = Scopes::new(Some(world.library())); - let mut vm = Vm::new(vt, route.track(), id, scopes); - - // Evaluate the code. - let code = root.cast::<ast::Code>().unwrap(); - let result = code.eval(&mut vm); - - // Handle control flow. - if let Some(flow) = vm.flow { - bail!(flow.forbidden()); - } - - result -} - -/// A virtual machine. -/// -/// Holds the state needed to [evaluate](eval) Typst sources. A new -/// virtual machine is created for each module evaluation and function call. -pub struct Vm<'a> { - /// The underlying virtual typesetter. - pub vt: Vt<'a>, - /// The language items. - items: LangItems, - /// The route of source ids the VM took to reach its current location. - route: Tracked<'a, Route<'a>>, - /// The current location. - location: FileId, - /// A control flow event that is currently happening. - flow: Option<FlowEvent>, - /// The stack of scopes. - scopes: Scopes<'a>, - /// The current call depth. - depth: usize, - /// A span that is currently traced. - traced: Option<Span>, -} - -impl<'a> Vm<'a> { - /// Create a new virtual machine. - fn new( - vt: Vt<'a>, - route: Tracked<'a, Route>, - location: FileId, - scopes: Scopes<'a>, - ) -> Self { - let traced = vt.tracer.span(location); - let items = vt.world.library().items.clone(); - Self { - vt, - items, - route, - location, - flow: None, - scopes, - depth: 0, - traced, - } - } - - /// Access the underlying world. - pub fn world(&self) -> Tracked<'a, dyn World + 'a> { - self.vt.world - } - - /// The location to which paths are relative currently. - pub fn location(&self) -> FileId { - self.location - } - - /// Define a variable in the current scope. - #[tracing::instrument(skip_all)] - pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) { - let value = value.into_value(); - if self.traced == Some(var.span()) { - self.vt.tracer.trace(value.clone()); - } - self.scopes.top.define(var.take(), value); - } -} - -/// A control flow event that occurred during evaluation. -#[derive(Debug, Clone, PartialEq)] -pub enum FlowEvent { - /// Stop iteration in a loop. - Break(Span), - /// Skip the remainder of the current iteration in a loop. - Continue(Span), - /// Stop execution of a function early, optionally returning an explicit - /// value. - Return(Span, Option<Value>), -} - -impl FlowEvent { - /// Return an error stating that this control flow is forbidden. - pub fn forbidden(&self) -> SourceError { - match *self { - Self::Break(span) => { - error!(span, "cannot break outside of loop") - } - Self::Continue(span) => { - error!(span, "cannot continue outside of loop") - } - Self::Return(span, _) => { - error!(span, "cannot return outside of function") - } - } - } -} - -/// A route of source ids. -#[derive(Default)] -pub struct Route<'a> { - // We need to override the constraint's lifetime here so that `Tracked` is - // covariant over the constraint. If it becomes invariant, we're in for a - // world of lifetime pain. - outer: Option<Tracked<'a, Self, <Route<'static> as Validate>::Constraint>>, - id: Option<FileId>, -} - -impl<'a> Route<'a> { - /// Create a new route with just one entry. - pub fn new(id: FileId) -> Self { - Self { id: Some(id), outer: None } - } - - /// Insert a new id into the route. - /// - /// You must guarantee that `outer` lives longer than the resulting - /// route is ever used. - pub fn insert(outer: Tracked<'a, Self>, id: FileId) -> Self { - Route { outer: Some(outer), id: Some(id) } - } - - /// Start tracking this locator. - /// - /// In comparison to [`Track::track`], this method skips this chain link - /// if it does not contribute anything. - pub fn track(&self) -> Tracked<'_, Self> { - match self.outer { - Some(outer) if self.id.is_none() => outer, - _ => Track::track(self), - } - } -} - -#[comemo::track] -impl<'a> Route<'a> { - /// Whether the given id is part of the route. - fn contains(&self, id: FileId) -> bool { - self.id == Some(id) || self.outer.map_or(false, |outer| outer.contains(id)) - } -} - -/// Traces which values existed for an expression at a span. -#[derive(Default, Clone)] -pub struct Tracer { - span: Option<Span>, - values: Vec<Value>, -} - -impl Tracer { - /// The maximum number of traced items. - pub const MAX: usize = 10; - - /// Create a new tracer, possibly with a span under inspection. - pub fn new(span: Option<Span>) -> Self { - Self { span, values: vec![] } - } - - /// Get the traced values. - pub fn finish(self) -> Vec<Value> { - self.values - } -} - -#[comemo::track] -impl Tracer { - /// The traced span if it is part of the given source file. - fn span(&self, id: FileId) -> Option<Span> { - if self.span.map(Span::id) == Some(id) { - self.span - } else { - None - } - } - - /// Trace a value for the span. - fn trace(&mut self, v: Value) { - if self.values.len() < Self::MAX { - self.values.push(v); - } - } -} - -/// Evaluate an expression. -pub(super) trait Eval { - /// The output of evaluating the expression. - type Output; - - /// Evaluate the expression to the output value. - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output>; -} - -impl Eval for ast::Markup { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - eval_markup(vm, &mut self.exprs()) - } -} - -/// Evaluate a stream of markup. -fn eval_markup( - vm: &mut Vm, - exprs: &mut impl Iterator<Item = ast::Expr>, -) -> SourceResult<Content> { - let flow = vm.flow.take(); - let mut seq = Vec::with_capacity(exprs.size_hint().1.unwrap_or_default()); - - while let Some(expr) = exprs.next() { - match expr { - ast::Expr::Set(set) => { - let styles = set.eval(vm)?; - if vm.flow.is_some() { - break; - } - - seq.push(eval_markup(vm, exprs)?.styled_with_map(styles)) - } - ast::Expr::Show(show) => { - let recipe = show.eval(vm)?; - if vm.flow.is_some() { - break; - } - - let tail = eval_markup(vm, exprs)?; - seq.push(tail.styled_with_recipe(vm, recipe)?) - } - expr => match expr.eval(vm)? { - Value::Label(label) => { - if let Some(elem) = - seq.iter_mut().rev().find(|node| !node.can::<dyn Unlabellable>()) - { - *elem = mem::take(elem).labelled(label); - } - } - value => seq.push(value.display().spanned(expr.span())), - }, - } - - if vm.flow.is_some() { - break; - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(Content::sequence(seq)) -} - -impl Eval for ast::Expr { - type Output = Value; - - #[tracing::instrument(name = "Expr::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.span(); - let forbidden = |name| { - error!(span, "{} is only allowed directly in code and content blocks", name) - }; - - let v = match self { - Self::Text(v) => v.eval(vm).map(Value::Content), - Self::Space(v) => v.eval(vm).map(Value::Content), - Self::Linebreak(v) => v.eval(vm).map(Value::Content), - Self::Parbreak(v) => v.eval(vm).map(Value::Content), - Self::Escape(v) => v.eval(vm), - Self::Shorthand(v) => v.eval(vm), - Self::SmartQuote(v) => v.eval(vm).map(Value::Content), - Self::Strong(v) => v.eval(vm).map(Value::Content), - Self::Emph(v) => v.eval(vm).map(Value::Content), - Self::Raw(v) => v.eval(vm).map(Value::Content), - Self::Link(v) => v.eval(vm).map(Value::Content), - Self::Label(v) => v.eval(vm), - Self::Ref(v) => v.eval(vm).map(Value::Content), - Self::Heading(v) => v.eval(vm).map(Value::Content), - Self::List(v) => v.eval(vm).map(Value::Content), - Self::Enum(v) => v.eval(vm).map(Value::Content), - Self::Term(v) => v.eval(vm).map(Value::Content), - Self::Equation(v) => v.eval(vm).map(Value::Content), - Self::Math(v) => v.eval(vm).map(Value::Content), - Self::MathIdent(v) => v.eval(vm), - Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content), - Self::MathDelimited(v) => v.eval(vm).map(Value::Content), - Self::MathAttach(v) => v.eval(vm).map(Value::Content), - Self::MathFrac(v) => v.eval(vm).map(Value::Content), - Self::MathRoot(v) => v.eval(vm).map(Value::Content), - Self::Ident(v) => v.eval(vm), - Self::None(v) => v.eval(vm), - Self::Auto(v) => v.eval(vm), - Self::Bool(v) => v.eval(vm), - Self::Int(v) => v.eval(vm), - Self::Float(v) => v.eval(vm), - Self::Numeric(v) => v.eval(vm), - Self::Str(v) => v.eval(vm), - Self::Code(v) => v.eval(vm), - Self::Content(v) => v.eval(vm).map(Value::Content), - Self::Array(v) => v.eval(vm).map(Value::Array), - Self::Dict(v) => v.eval(vm).map(Value::Dict), - Self::Parenthesized(v) => v.eval(vm), - Self::FieldAccess(v) => v.eval(vm), - Self::FuncCall(v) => v.eval(vm), - Self::Closure(v) => v.eval(vm), - Self::Unary(v) => v.eval(vm), - Self::Binary(v) => v.eval(vm), - Self::Let(v) => v.eval(vm), - Self::DestructAssign(v) => v.eval(vm), - Self::Set(_) => bail!(forbidden("set")), - Self::Show(_) => bail!(forbidden("show")), - Self::Conditional(v) => v.eval(vm), - Self::While(v) => v.eval(vm), - Self::For(v) => v.eval(vm), - Self::Import(v) => v.eval(vm), - Self::Include(v) => v.eval(vm).map(Value::Content), - Self::Break(v) => v.eval(vm), - Self::Continue(v) => v.eval(vm), - Self::Return(v) => v.eval(vm), - }? - .spanned(span); - - if vm.traced == Some(span) { - vm.vt.tracer.trace(v.clone()); - } - - Ok(v) - } -} - -impl ast::Expr { - fn eval_display(&self, vm: &mut Vm) -> SourceResult<Content> { - Ok(self.eval(vm)?.display().spanned(self.span())) - } -} - -impl Eval for ast::Text { - type Output = Content; - - #[tracing::instrument(name = "Text::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.text)(self.get().clone())) - } -} - -impl Eval for ast::Space { - type Output = Content; - - #[tracing::instrument(name = "Space::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.space)()) - } -} - -impl Eval for ast::Linebreak { - type Output = Content; - - #[tracing::instrument(name = "Linebreak::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.linebreak)()) - } -} - -impl Eval for ast::Parbreak { - type Output = Content; - - #[tracing::instrument(name = "Parbreak::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.parbreak)()) - } -} - -impl Eval for ast::Escape { - type Output = Value; - - #[tracing::instrument(name = "Escape::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Symbol(Symbol::new(self.get()))) - } -} - -impl Eval for ast::Shorthand { - type Output = Value; - - #[tracing::instrument(name = "Shorthand::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Symbol(Symbol::new(self.get()))) - } -} - -impl Eval for ast::SmartQuote { - type Output = Content; - - #[tracing::instrument(name = "SmartQuote::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.smart_quote)(self.double())) - } -} - -impl Eval for ast::Strong { - type Output = Content; - - #[tracing::instrument(name = "Strong::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.strong)(self.body().eval(vm)?)) - } -} - -impl Eval for ast::Emph { - type Output = Content; - - #[tracing::instrument(name = "Emph::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.emph)(self.body().eval(vm)?)) - } -} - -impl Eval for ast::Raw { - type Output = Content; - - #[tracing::instrument(name = "Raw::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let text = self.text(); - let lang = self.lang().map(Into::into); - let block = self.block(); - Ok((vm.items.raw)(text, lang, block)) - } -} - -impl Eval for ast::Link { - type Output = Content; - - #[tracing::instrument(name = "Link::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.link)(self.get().clone())) - } -} - -impl Eval for ast::Label { - type Output = Value; - - #[tracing::instrument(name = "Label::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Label(Label(self.get().into()))) - } -} - -impl Eval for ast::Ref { - type Output = Content; - - #[tracing::instrument(name = "Ref::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let label = Label(self.target().into()); - let supplement = self.supplement().map(|block| block.eval(vm)).transpose()?; - Ok((vm.items.reference)(label, supplement)) - } -} - -impl Eval for ast::Heading { - type Output = Content; - - #[tracing::instrument(name = "Heading::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let level = self.level(); - let body = self.body().eval(vm)?; - Ok((vm.items.heading)(level, body)) - } -} - -impl Eval for ast::ListItem { - type Output = Content; - - #[tracing::instrument(name = "ListItem::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.list_item)(self.body().eval(vm)?)) - } -} - -impl Eval for ast::EnumItem { - type Output = Content; - - #[tracing::instrument(name = "EnumItem::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let number = self.number(); - let body = self.body().eval(vm)?; - Ok((vm.items.enum_item)(number, body)) - } -} - -impl Eval for ast::TermItem { - type Output = Content; - - #[tracing::instrument(name = "TermItem::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let term = self.term().eval(vm)?; - let description = self.description().eval(vm)?; - Ok((vm.items.term_item)(term, description)) - } -} - -impl Eval for ast::Equation { - type Output = Content; - - #[tracing::instrument(name = "Equation::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let body = self.body().eval(vm)?; - let block = self.block(); - Ok((vm.items.equation)(body, block)) - } -} - -impl Eval for ast::Math { - type Output = Content; - - #[tracing::instrument(name = "Math::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::sequence( - self.exprs() - .map(|expr| expr.eval_display(vm)) - .collect::<SourceResult<Vec<_>>>()?, - )) - } -} - -impl Eval for ast::MathIdent { - type Output = Value; - - #[tracing::instrument(name = "MathIdent::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.get_in_math(self).cloned().at(self.span()) - } -} - -impl Eval for ast::MathAlignPoint { - type Output = Content; - - #[tracing::instrument(name = "MathAlignPoint::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.math_align_point)()) - } -} - -impl Eval for ast::MathDelimited { - type Output = Content; - - #[tracing::instrument(name = "MathDelimited::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let open = self.open().eval_display(vm)?; - let body = self.body().eval(vm)?; - let close = self.close().eval_display(vm)?; - Ok((vm.items.math_delimited)(open, body, close)) - } -} - -impl Eval for ast::MathAttach { - type Output = Content; - - #[tracing::instrument(name = "MathAttach::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let base = self.base().eval_display(vm)?; - let top = self.top().map(|expr| expr.eval_display(vm)).transpose()?; - let bottom = self.bottom().map(|expr| expr.eval_display(vm)).transpose()?; - Ok((vm.items.math_attach)(base, top, bottom, None, None, None, None)) - } -} - -impl Eval for ast::MathFrac { - type Output = Content; - - #[tracing::instrument(name = "MathFrac::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let num = self.num().eval_display(vm)?; - let denom = self.denom().eval_display(vm)?; - Ok((vm.items.math_frac)(num, denom)) - } -} - -impl Eval for ast::MathRoot { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let index = self.index().map(|i| (vm.items.text)(eco_format!("{i}"))); - let radicand = self.radicand().eval_display(vm)?; - Ok((vm.items.math_root)(index, radicand)) - } -} - -impl Eval for ast::Ident { - type Output = Value; - - #[tracing::instrument(name = "Ident::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.get(self).cloned().at(self.span()) - } -} - -impl Eval for ast::None { - type Output = Value; - - #[tracing::instrument(name = "None::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::None) - } -} - -impl Eval for ast::Auto { - type Output = Value; - - #[tracing::instrument(name = "Auto::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Auto) - } -} - -impl Eval for ast::Bool { - type Output = Value; - - #[tracing::instrument(name = "Bool::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Bool(self.get())) - } -} - -impl Eval for ast::Int { - type Output = Value; - - #[tracing::instrument(name = "Int::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Int(self.get())) - } -} - -impl Eval for ast::Float { - type Output = Value; - - #[tracing::instrument(name = "Float::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Float(self.get())) - } -} - -impl Eval for ast::Numeric { - type Output = Value; - - #[tracing::instrument(name = "Numeric::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::numeric(self.get())) - } -} - -impl Eval for ast::Str { - type Output = Value; - - #[tracing::instrument(name = "Str::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Str(self.get().into())) - } -} - -impl Eval for ast::CodeBlock { - type Output = Value; - - #[tracing::instrument(name = "CodeBlock::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.enter(); - let output = self.body().eval(vm)?; - vm.scopes.exit(); - Ok(output) - } -} - -impl Eval for ast::Code { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - eval_code(vm, &mut self.exprs()) - } -} - -/// Evaluate a stream of expressions. -fn eval_code( - vm: &mut Vm, - exprs: &mut impl Iterator<Item = ast::Expr>, -) -> SourceResult<Value> { - let flow = vm.flow.take(); - let mut output = Value::None; - - while let Some(expr) = exprs.next() { - let span = expr.span(); - let value = match expr { - ast::Expr::Set(set) => { - let styles = set.eval(vm)?; - if vm.flow.is_some() { - break; - } - - let tail = eval_code(vm, exprs)?.display(); - Value::Content(tail.styled_with_map(styles)) - } - ast::Expr::Show(show) => { - let recipe = show.eval(vm)?; - if vm.flow.is_some() { - break; - } - - let tail = eval_code(vm, exprs)?.display(); - Value::Content(tail.styled_with_recipe(vm, recipe)?) - } - _ => expr.eval(vm)?, - }; - - output = ops::join(output, value).at(span)?; - - if vm.flow.is_some() { - break; - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(output) -} - -impl Eval for ast::ContentBlock { - type Output = Content; - - #[tracing::instrument(name = "ContentBlock::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.enter(); - let content = self.body().eval(vm)?; - vm.scopes.exit(); - Ok(content) - } -} - -impl Eval for ast::Parenthesized { - type Output = Value; - - #[tracing::instrument(name = "Parenthesized::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - self.expr().eval(vm) - } -} - -impl Eval for ast::Array { - type Output = Array; - - #[tracing::instrument(skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let items = self.items(); - - let mut vec = EcoVec::with_capacity(items.size_hint().0); - for item in items { - match item { - ast::ArrayItem::Pos(expr) => vec.push(expr.eval(vm)?), - ast::ArrayItem::Spread(expr) => match expr.eval(vm)? { - Value::None => {} - Value::Array(array) => vec.extend(array.into_iter()), - v => bail!(expr.span(), "cannot spread {} into array", v.type_name()), - }, - } - } - - Ok(vec.into()) - } -} - -impl Eval for ast::Dict { - type Output = Dict; - - #[tracing::instrument(skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let mut map = indexmap::IndexMap::new(); - - for item in self.items() { - match item { - ast::DictItem::Named(named) => { - map.insert(named.name().take().into(), named.expr().eval(vm)?); - } - ast::DictItem::Keyed(keyed) => { - map.insert(keyed.key().get().into(), keyed.expr().eval(vm)?); - } - ast::DictItem::Spread(expr) => match expr.eval(vm)? { - Value::None => {} - Value::Dict(dict) => map.extend(dict.into_iter()), - v => bail!( - expr.span(), - "cannot spread {} into dictionary", - v.type_name() - ), - }, - } - } - - Ok(map.into()) - } -} - -impl Eval for ast::Unary { - type Output = Value; - - #[tracing::instrument(name = "Unary::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.expr().eval(vm)?; - let result = match self.op() { - ast::UnOp::Pos => ops::pos(value), - ast::UnOp::Neg => ops::neg(value), - ast::UnOp::Not => ops::not(value), - }; - result.at(self.span()) - } -} - -impl Eval for ast::Binary { - type Output = Value; - - #[tracing::instrument(name = "Binary::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - match self.op() { - ast::BinOp::Add => self.apply(vm, ops::add), - ast::BinOp::Sub => self.apply(vm, ops::sub), - ast::BinOp::Mul => self.apply(vm, ops::mul), - ast::BinOp::Div => self.apply(vm, ops::div), - ast::BinOp::And => self.apply(vm, ops::and), - ast::BinOp::Or => self.apply(vm, ops::or), - ast::BinOp::Eq => self.apply(vm, ops::eq), - ast::BinOp::Neq => self.apply(vm, ops::neq), - ast::BinOp::Lt => self.apply(vm, ops::lt), - ast::BinOp::Leq => self.apply(vm, ops::leq), - ast::BinOp::Gt => self.apply(vm, ops::gt), - ast::BinOp::Geq => self.apply(vm, ops::geq), - ast::BinOp::In => self.apply(vm, ops::in_), - ast::BinOp::NotIn => self.apply(vm, ops::not_in), - ast::BinOp::Assign => self.assign(vm, |_, b| Ok(b)), - ast::BinOp::AddAssign => self.assign(vm, ops::add), - ast::BinOp::SubAssign => self.assign(vm, ops::sub), - ast::BinOp::MulAssign => self.assign(vm, ops::mul), - ast::BinOp::DivAssign => self.assign(vm, ops::div), - } - } -} - -impl ast::Binary { - /// Apply a basic binary operation. - fn apply( - &self, - vm: &mut Vm, - op: fn(Value, Value) -> StrResult<Value>, - ) -> SourceResult<Value> { - let lhs = self.lhs().eval(vm)?; - - // Short-circuit boolean operations. - if (self.op() == ast::BinOp::And && lhs == Value::Bool(false)) - || (self.op() == ast::BinOp::Or && lhs == Value::Bool(true)) - { - return Ok(lhs); - } - - let rhs = self.rhs().eval(vm)?; - op(lhs, rhs).at(self.span()) - } - - /// Apply an assignment operation. - fn assign( - &self, - vm: &mut Vm, - op: fn(Value, Value) -> StrResult<Value>, - ) -> SourceResult<Value> { - let rhs = self.rhs().eval(vm)?; - let lhs = self.lhs(); - - // An assignment to a dictionary field is different from a normal access - // since it can create the field instead of just modifying it. - if self.op() == ast::BinOp::Assign { - if let ast::Expr::FieldAccess(access) = &lhs { - let dict = access.access_dict(vm)?; - dict.insert(access.field().take().into(), rhs); - return Ok(Value::None); - } - } - - let location = self.lhs().access(vm)?; - let lhs = std::mem::take(&mut *location); - *location = op(lhs, rhs).at(self.span())?; - Ok(Value::None) - } -} - -impl Eval for ast::FieldAccess { - type Output = Value; - - #[tracing::instrument(name = "FieldAccess::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.target().eval(vm)?; - let field = self.field(); - value.field(&field).at(field.span()) - } -} - -impl Eval for ast::FuncCall { - type Output = Value; - - #[tracing::instrument(name = "FuncCall::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.span(); - if vm.depth >= MAX_CALL_DEPTH { - bail!(span, "maximum function call depth exceeded"); - } - - let callee = self.callee(); - let in_math = in_math(&callee); - let callee_span = callee.span(); - let args = self.args(); - - // Try to evaluate as a method call. This is possible if the callee is a - // field access and does not evaluate to a module. - let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee { - let target = access.target(); - let field = access.field(); - let field_span = field.span(); - let field = field.take(); - let point = || Tracepoint::Call(Some(field.clone())); - if methods::is_mutating(&field) { - let args = args.eval(vm)?; - let target = target.access(vm)?; - - // Prioritize a function's own methods (with, where) over its - // fields. This is fine as we define each field of a function, - // if it has any. - // ('methods_on' will be empty for Symbol and Module - their - // method calls always refer to their fields.) - if !matches!(target, Value::Symbol(_) | Value::Module(_) | Value::Func(_)) - || methods_on(target.type_name()).iter().any(|(m, _)| m == &field) - { - return methods::call_mut(target, &field, args, span).trace( - vm.world(), - point, - span, - ); - } - (target.field(&field).at(field_span)?, args) - } else { - let target = target.eval(vm)?; - let args = args.eval(vm)?; - - if !matches!(target, Value::Symbol(_) | Value::Module(_) | Value::Func(_)) - || methods_on(target.type_name()).iter().any(|(m, _)| m == &field) - { - return methods::call(vm, target, &field, args, span).trace( - vm.world(), - point, - span, - ); - } - (target.field(&field).at(field_span)?, args) - } - } else { - (callee.eval(vm)?, args.eval(vm)?) - }; - - // Handle math special cases for non-functions: - // Combining accent symbols apply themselves while everything else - // simply displays the arguments verbatim. - if in_math && !matches!(callee, Value::Func(_)) { - if let Value::Symbol(sym) = &callee { - let c = sym.get(); - if let Some(accent) = Symbol::combining_accent(c) { - let base = args.expect("base")?; - args.finish()?; - return Ok(Value::Content((vm.items.math_accent)(base, accent))); - } - } - let mut body = Content::empty(); - for (i, arg) in args.all::<Content>()?.into_iter().enumerate() { - if i > 0 { - body += (vm.items.text)(','.into()); - } - body += arg; - } - return Ok(Value::Content( - callee.display().spanned(callee_span) - + (vm.items.math_delimited)( - (vm.items.text)('('.into()), - body, - (vm.items.text)(')'.into()), - ), - )); - } - - let callee = callee.cast::<Func>().at(callee_span)?; - let point = || Tracepoint::Call(callee.name().map(Into::into)); - let f = || callee.call_vm(vm, args).trace(vm.world(), point, span); - - // Stacker is broken on WASM. - #[cfg(target_arch = "wasm32")] - return f(); - - #[cfg(not(target_arch = "wasm32"))] - stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, f) - } -} - -fn in_math(expr: &ast::Expr) -> bool { - match expr { - ast::Expr::MathIdent(_) => true, - ast::Expr::FieldAccess(access) => in_math(&access.target()), - _ => false, - } -} - -impl Eval for ast::Args { - type Output = Args; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let mut items = EcoVec::new(); - - for arg in self.items() { - let span = arg.span(); - match arg { - ast::Arg::Pos(expr) => { - items.push(Arg { - span, - name: None, - value: Spanned::new(expr.eval(vm)?, expr.span()), - }); - } - ast::Arg::Named(named) => { - items.push(Arg { - span, - name: Some(named.name().take().into()), - value: Spanned::new(named.expr().eval(vm)?, named.expr().span()), - }); - } - ast::Arg::Spread(expr) => match expr.eval(vm)? { - Value::None => {} - Value::Array(array) => { - items.extend(array.into_iter().map(|value| Arg { - span, - name: None, - value: Spanned::new(value, span), - })); - } - Value::Dict(dict) => { - items.extend(dict.into_iter().map(|(key, value)| Arg { - span, - name: Some(key), - value: Spanned::new(value, span), - })); - } - Value::Args(args) => items.extend(args.items), - v => bail!(expr.span(), "cannot spread {}", v.type_name()), - }, - } - } - - Ok(Args { span: self.span(), items }) - } -} - -impl Eval for ast::Closure { - type Output = Value; - - #[tracing::instrument(name = "Closure::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - // The closure's name is defined by its let binding if there's one. - let name = self.name(); - - // Collect captured variables. - let captured = { - let mut visitor = CapturesVisitor::new(&vm.scopes); - visitor.visit(self.as_untyped()); - visitor.finish() - }; - - // Collect parameters and an optional sink parameter. - let mut params = Vec::new(); - for param in self.params().children() { - match param { - ast::Param::Pos(pattern) => params.push(Param::Pos(pattern)), - ast::Param::Named(named) => { - params.push(Param::Named(named.name(), named.expr().eval(vm)?)); - } - ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())), - } - } - - // Define the closure. - let closure = Closure { - location: vm.location, - name, - captured, - params, - body: self.body(), - }; - - Ok(Value::Func(Func::from(closure).spanned(self.params().span()))) - } -} - -impl ast::Pattern { - fn destruct_array<F>( - &self, - vm: &mut Vm, - value: Array, - f: F, - destruct: &ast::Destructuring, - ) -> SourceResult<Value> - where - F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, - { - let mut i = 0; - let len = value.as_slice().len(); - for p in destruct.bindings() { - match p { - ast::DestructuringKind::Normal(expr) => { - let Ok(v) = value.at(i as i64, None) else { - bail!(expr.span(), "not enough elements to destructure"); - }; - f(vm, expr, v.clone())?; - i += 1; - } - ast::DestructuringKind::Sink(spread) => { - let sink_size = (1 + len).checked_sub(destruct.bindings().count()); - let sink = sink_size.and_then(|s| value.as_slice().get(i..i + s)); - if let (Some(sink_size), Some(sink)) = (sink_size, sink) { - if let Some(expr) = spread.expr() { - f(vm, expr, Value::Array(sink.into()))?; - } - i += sink_size; - } else { - bail!(self.span(), "not enough elements to destructure") - } - } - ast::DestructuringKind::Named(named) => { - bail!(named.span(), "cannot destructure named elements from an array") - } - ast::DestructuringKind::Placeholder(underscore) => { - if i < len { - i += 1 - } else { - bail!(underscore.span(), "not enough elements to destructure") - } - } - } - } - if i < len { - bail!(self.span(), "too many elements to destructure"); - } - - Ok(Value::None) - } - - fn destruct_dict<F>( - &self, - vm: &mut Vm, - dict: Dict, - f: F, - destruct: &ast::Destructuring, - ) -> SourceResult<Value> - where - F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, - { - let mut sink = None; - let mut used = HashSet::new(); - for p in destruct.bindings() { - match p { - ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => { - let v = dict - .at(&ident, None) - .map_err(|_| "destructuring key not found in dictionary") - .at(ident.span())?; - f(vm, ast::Expr::Ident(ident.clone()), v.clone())?; - used.insert(ident.take()); - } - ast::DestructuringKind::Sink(spread) => sink = spread.expr(), - ast::DestructuringKind::Named(named) => { - let name = named.name(); - let v = dict - .at(&name, None) - .map_err(|_| "destructuring key not found in dictionary") - .at(name.span())?; - f(vm, named.expr(), v.clone())?; - used.insert(name.take()); - } - ast::DestructuringKind::Placeholder(_) => {} - ast::DestructuringKind::Normal(expr) => { - bail!(expr.span(), "expected key, found expression"); - } - } - } - - if let Some(expr) = sink { - let mut sink = Dict::new(); - for (key, value) in dict { - if !used.contains(key.as_str()) { - sink.insert(key, value); - } - } - f(vm, expr, Value::Dict(sink))?; - } - - Ok(Value::None) - } - - /// Destruct the given value into the pattern and apply the function to each binding. - #[tracing::instrument(skip_all)] - fn apply<T>(&self, vm: &mut Vm, value: Value, f: T) -> SourceResult<Value> - where - T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, - { - match self { - ast::Pattern::Normal(expr) => { - f(vm, expr.clone(), value)?; - Ok(Value::None) - } - ast::Pattern::Placeholder(_) => Ok(Value::None), - ast::Pattern::Destructuring(destruct) => match value { - Value::Array(value) => self.destruct_array(vm, value, f, destruct), - Value::Dict(value) => self.destruct_dict(vm, value, f, destruct), - _ => bail!(self.span(), "cannot destructure {}", value.type_name()), - }, - } - } - - /// Destruct the value into the pattern by binding. - pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { - self.apply(vm, value, |vm, expr, value| match expr { - ast::Expr::Ident(ident) => { - vm.define(ident, value); - Ok(Value::None) - } - _ => bail!(expr.span(), "nested patterns are currently not supported"), - }) - } - - /// Destruct the value into the pattern by assignment. - pub fn assign(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { - self.apply(vm, value, |vm, expr, value| { - let location = expr.access(vm)?; - *location = value; - Ok(Value::None) - }) - } -} - -impl Eval for ast::LetBinding { - type Output = Value; - - #[tracing::instrument(name = "LetBinding::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = match self.init() { - Some(expr) => expr.eval(vm)?, - None => Value::None, - }; - - match self.kind() { - ast::LetBindingKind::Normal(pattern) => pattern.define(vm, value), - ast::LetBindingKind::Closure(ident) => { - vm.define(ident, value); - Ok(Value::None) - } - } - } -} - -impl Eval for ast::DestructAssignment { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.value().eval(vm)?; - self.pattern().assign(vm, value)?; - Ok(Value::None) - } -} - -impl Eval for ast::SetRule { - type Output = Styles; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if let Some(condition) = self.condition() { - if !condition.eval(vm)?.cast::<bool>().at(condition.span())? { - return Ok(Styles::new()); - } - } - - let target = self.target(); - let target = target - .eval(vm)? - .cast::<Func>() - .and_then(|func| { - func.element().ok_or_else(|| { - "only element functions can be used in set rules".into() - }) - }) - .at(target.span())?; - let args = self.args().eval(vm)?; - Ok(target.set(args)?.spanned(self.span())) - } -} - -impl Eval for ast::ShowRule { - type Output = Recipe; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let selector = self - .selector() - .map(|sel| sel.eval(vm)?.cast::<ShowableSelector>().at(sel.span())) - .transpose()? - .map(|selector| selector.0); - - let transform = self.transform(); - let span = transform.span(); - - let transform = match transform { - ast::Expr::Set(set) => Transform::Style(set.eval(vm)?), - expr => expr.eval(vm)?.cast::<Transform>().at(span)?, - }; - - Ok(Recipe { span, selector, transform }) - } -} - -impl Eval for ast::Conditional { - type Output = Value; - - #[tracing::instrument(name = "Conditional::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let condition = self.condition(); - if condition.eval(vm)?.cast::<bool>().at(condition.span())? { - self.if_body().eval(vm) - } else if let Some(else_body) = self.else_body() { - else_body.eval(vm) - } else { - Ok(Value::None) - } - } -} - -impl Eval for ast::WhileLoop { - type Output = Value; - - #[tracing::instrument(name = "WhileLoop::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let flow = vm.flow.take(); - let mut output = Value::None; - let mut i = 0; - - let condition = self.condition(); - let body = self.body(); - - while condition.eval(vm)?.cast::<bool>().at(condition.span())? { - if i == 0 - && is_invariant(condition.as_untyped()) - && !can_diverge(body.as_untyped()) - { - bail!(condition.span(), "condition is always true"); - } else if i >= MAX_ITERATIONS { - bail!(self.span(), "loop seems to be infinite"); - } - - let value = body.eval(vm)?; - output = ops::join(output, value).at(body.span())?; - - match vm.flow { - Some(FlowEvent::Break(_)) => { - vm.flow = None; - break; - } - Some(FlowEvent::Continue(_)) => vm.flow = None, - Some(FlowEvent::Return(..)) => break, - None => {} - } - - i += 1; - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(output) - } -} - -/// Whether the expression always evaluates to the same value. -fn is_invariant(expr: &SyntaxNode) -> bool { - match expr.cast() { - Some(ast::Expr::Ident(_)) => false, - Some(ast::Expr::MathIdent(_)) => false, - Some(ast::Expr::FieldAccess(access)) => { - is_invariant(access.target().as_untyped()) - } - Some(ast::Expr::FuncCall(call)) => { - is_invariant(call.callee().as_untyped()) - && is_invariant(call.args().as_untyped()) - } - _ => expr.children().all(is_invariant), - } -} - -/// Whether the expression contains a break or return. -fn can_diverge(expr: &SyntaxNode) -> bool { - matches!(expr.kind(), SyntaxKind::Break | SyntaxKind::Return) - || expr.children().any(can_diverge) -} - -impl Eval for ast::ForLoop { - type Output = Value; - - #[tracing::instrument(name = "ForLoop::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let flow = vm.flow.take(); - let mut output = Value::None; - - macro_rules! iter { - (for $pat:ident in $iter:expr) => {{ - vm.scopes.enter(); - - #[allow(unused_parens)] - for value in $iter { - $pat.define(vm, value.into_value())?; - - let body = self.body(); - let value = body.eval(vm)?; - output = ops::join(output, value).at(body.span())?; - - match vm.flow { - Some(FlowEvent::Break(_)) => { - vm.flow = None; - break; - } - Some(FlowEvent::Continue(_)) => vm.flow = None, - Some(FlowEvent::Return(..)) => break, - None => {} - } - } - - vm.scopes.exit(); - }}; - } - - let iter = self.iter().eval(vm)?; - let pattern = self.pattern(); - - match (&pattern, iter.clone()) { - (ast::Pattern::Normal(_), Value::Str(string)) => { - // Iterate over graphemes of string. - iter!(for pattern in string.as_str().graphemes(true)); - } - (_, Value::Dict(dict)) => { - // Iterate over pairs of dict. - iter!(for pattern in dict.pairs()); - } - (_, Value::Array(array)) => { - // Iterate over values of array. - iter!(for pattern in array); - } - (ast::Pattern::Normal(_), _) => { - bail!(self.iter().span(), "cannot loop over {}", iter.type_name()); - } - (_, _) => { - bail!(pattern.span(), "cannot destructure values of {}", iter.type_name()) - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(output) - } -} - -/// Applies imports from `import` to the current scope. -fn apply_imports<V: IntoValue>( - imports: Option<ast::Imports>, - vm: &mut Vm, - source_value: V, - name: impl Fn(&V) -> EcoString, - scope: impl Fn(&V) -> &Scope, -) -> SourceResult<()> { - match imports { - None => { - vm.scopes.top.define(name(&source_value), source_value); - } - Some(ast::Imports::Wildcard) => { - for (var, value) in scope(&source_value).iter() { - vm.scopes.top.define(var.clone(), value.clone()); - } - } - Some(ast::Imports::Items(idents)) => { - let mut errors = vec![]; - let scope = scope(&source_value); - for ident in idents { - if let Some(value) = scope.get(&ident) { - vm.define(ident, value.clone()); - } else { - errors.push(error!(ident.span(), "unresolved import")); - } - } - if !errors.is_empty() { - return Err(Box::new(errors)); - } - } - } - - Ok(()) -} - -impl Eval for ast::ModuleImport { - type Output = Value; - - #[tracing::instrument(name = "ModuleImport::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.source().span(); - let source = self.source().eval(vm)?; - if let Value::Func(func) = source { - if func.info().is_none() { - bail!(span, "cannot import from user-defined functions"); - } - apply_imports( - self.imports(), - vm, - func, - |func| func.info().unwrap().name.into(), - |func| &func.info().unwrap().scope, - )?; - } else { - let module = import(vm, source, span, true)?; - apply_imports( - self.imports(), - vm, - module, - |module| module.name().clone(), - |module| module.scope(), - )?; - } - - Ok(Value::None) - } -} - -impl Eval for ast::ModuleInclude { - type Output = Content; - - #[tracing::instrument(name = "ModuleInclude::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.source().span(); - let source = self.source().eval(vm)?; - let module = import(vm, source, span, false)?; - Ok(module.content()) - } -} - -/// Process an import of a module relative to the current location. -fn import( - vm: &mut Vm, - source: Value, - span: Span, - accept_functions: bool, -) -> SourceResult<Module> { - let path = match source { - Value::Str(path) => path, - Value::Module(module) => return Ok(module), - v => { - if accept_functions { - bail!(span, "expected path, module or function, found {}", v.type_name()) - } else { - bail!(span, "expected path or module, found {}", v.type_name()) - } - } - }; - - // Handle package and file imports. - let path = path.as_str(); - if path.starts_with('@') { - let spec = path.parse::<PackageSpec>().at(span)?; - import_package(vm, spec, span) - } else { - import_file(vm, path, span) - } -} - -/// Import an external package. -fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Module> { - // Evaluate the manifest. - let manifest_id = FileId::new(Some(spec.clone()), Path::new("/typst.toml")); - let bytes = vm.world().file(manifest_id).at(span)?; - let manifest = PackageManifest::parse(&bytes).at(span)?; - manifest.validate(&spec).at(span)?; - - // Evaluate the entry point. - let entrypoint_id = manifest_id.join(&manifest.package.entrypoint).at(span)?; - let source = vm.world().source(entrypoint_id).at(span)?; - let point = || Tracepoint::Import; - Ok(eval(vm.world(), vm.route, TrackedMut::reborrow_mut(&mut vm.vt.tracer), &source) - .trace(vm.world(), point, span)? - .with_name(manifest.package.name)) -} - -/// Import a file from a path. -fn import_file(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> { - // Load the source file. - let world = vm.world(); - let id = vm.location().join(path).at(span)?; - let source = world.source(id).at(span)?; - - // Prevent cyclic importing. - if vm.route.contains(source.id()) { - bail!(span, "cyclic import"); - } - - // Evaluate the file. - let point = || Tracepoint::Import; - eval(world, vm.route, TrackedMut::reborrow_mut(&mut vm.vt.tracer), &source) - .trace(world, point, span) -} - -impl Eval for ast::LoopBreak { - type Output = Value; - - #[tracing::instrument(name = "LoopBreak::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if vm.flow.is_none() { - vm.flow = Some(FlowEvent::Break(self.span())); - } - Ok(Value::None) - } -} - -impl Eval for ast::LoopContinue { - type Output = Value; - - #[tracing::instrument(name = "LoopContinue::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if vm.flow.is_none() { - vm.flow = Some(FlowEvent::Continue(self.span())); - } - Ok(Value::None) - } -} - -impl Eval for ast::FuncReturn { - type Output = Value; - - #[tracing::instrument(name = "FuncReturn::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.body().map(|body| body.eval(vm)).transpose()?; - if vm.flow.is_none() { - vm.flow = Some(FlowEvent::Return(self.span(), value)); - } - Ok(Value::None) - } -} - -/// Access an expression mutably. -trait Access { - /// Access the value. - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value>; -} - -impl Access for ast::Expr { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - match self { - Self::Ident(v) => v.access(vm), - Self::Parenthesized(v) => v.access(vm), - Self::FieldAccess(v) => v.access(vm), - Self::FuncCall(v) => v.access(vm), - _ => { - let _ = self.eval(vm)?; - bail!(self.span(), "cannot mutate a temporary value"); - } - } - } -} - -impl Access for ast::Ident { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - let span = self.span(); - let value = vm.scopes.get_mut(self).at(span)?; - if vm.traced == Some(span) { - vm.vt.tracer.trace(value.clone()); - } - Ok(value) - } -} - -impl Access for ast::Parenthesized { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - self.expr().access(vm) - } -} - -impl Access for ast::FieldAccess { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - self.access_dict(vm)?.at_mut(&self.field().take()).at(self.span()) - } -} - -impl ast::FieldAccess { - fn access_dict<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Dict> { - match self.target().access(vm)? { - Value::Dict(dict) => Ok(dict), - value => bail!( - self.target().span(), - "expected dictionary, found {}", - value.type_name(), - ), - } - } -} - -impl Access for ast::FuncCall { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - if let ast::Expr::FieldAccess(access) = self.callee() { - let method = access.field().take(); - if methods::is_accessor(&method) { - let span = self.span(); - let world = vm.world(); - let args = self.args().eval(vm)?; - let value = access.target().access(vm)?; - let result = methods::call_access(value, &method, args, span); - let point = || Tracepoint::Call(Some(method.clone())); - return result.trace(world, point, span); - } - } - - let _ = self.eval(vm)?; - bail!(self.span(), "cannot mutate a temporary value"); - } -} diff --git a/src/eval/module.rs b/src/eval/module.rs deleted file mode 100644 index 0bc6bf38..00000000 --- a/src/eval/module.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::sync::Arc; - -use ecow::{eco_format, EcoString}; - -use super::{Content, Scope, Value}; -use crate::diag::StrResult; - -/// An evaluated module, ready for importing or typesetting. -/// -/// Values of this type are cheap to clone and hash. -#[derive(Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Module { - /// The module's name. - name: EcoString, - /// The reference-counted inner fields. - inner: Arc<Repr>, -} - -/// The internal representation. -#[derive(Clone, Hash)] -struct Repr { - /// The top-level definitions that were bound in this module. - scope: Scope, - /// The module's layoutable contents. - content: Content, -} - -impl Module { - /// Create a new module. - pub fn new(name: impl Into<EcoString>) -> Self { - Self { - name: name.into(), - inner: Arc::new(Repr { scope: Scope::new(), content: Content::empty() }), - } - } - - /// Update the module's name. - pub fn with_name(mut self, name: impl Into<EcoString>) -> Self { - self.name = name.into(); - self - } - - /// Update the module's scope. - pub fn with_scope(mut self, scope: Scope) -> Self { - Arc::make_mut(&mut self.inner).scope = scope; - self - } - - /// Update the module's content. - pub fn with_content(mut self, content: Content) -> Self { - Arc::make_mut(&mut self.inner).content = content; - self - } - - /// Get the module's name. - pub fn name(&self) -> &EcoString { - &self.name - } - - /// Access the module's scope. - pub fn scope(&self) -> &Scope { - &self.inner.scope - } - - /// Access the module's scope, mutably. - pub fn scope_mut(&mut self) -> &mut Scope { - &mut Arc::make_mut(&mut self.inner).scope - } - - /// Try to access a definition in the module. - pub fn get(&self, name: &str) -> StrResult<&Value> { - self.scope().get(name).ok_or_else(|| { - eco_format!("module `{}` does not contain `{name}`", self.name()) - }) - } - - /// Extract the module's content. - pub fn content(self) -> Content { - match Arc::try_unwrap(self.inner) { - Ok(repr) => repr.content, - Err(arc) => arc.content.clone(), - } - } -} - -impl Debug for Module { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "<module {}>", self.name()) - } -} - -impl PartialEq for Module { - fn eq(&self, other: &Self) -> bool { - self.name == other.name && Arc::ptr_eq(&self.inner, &other.inner) - } -} diff --git a/src/eval/none.rs b/src/eval/none.rs deleted file mode 100644 index ab7644a7..00000000 --- a/src/eval/none.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; - -use super::{cast, CastInfo, FromValue, IntoValue, Reflect, Value}; -use crate::diag::StrResult; - -/// A value that indicates the absence of any other value. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct NoneValue; - -impl Reflect for NoneValue { - fn describe() -> CastInfo { - CastInfo::Type("none") - } - - fn castable(value: &Value) -> bool { - matches!(value, Value::None) - } -} - -impl IntoValue for NoneValue { - fn into_value(self) -> Value { - Value::None - } -} - -impl FromValue for NoneValue { - fn from_value(value: Value) -> StrResult<Self> { - match value { - Value::None => Ok(Self), - _ => Err(Self::error(&value)), - } - } -} - -impl Debug for NoneValue { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("none") - } -} - -cast! { - (), - self => Value::None, - _: NoneValue => (), -} - -impl<T: Reflect> Reflect for Option<T> { - fn describe() -> CastInfo { - T::describe() + NoneValue::describe() - } - - fn castable(value: &Value) -> bool { - NoneValue::castable(value) || T::castable(value) - } -} - -impl<T: IntoValue> IntoValue for Option<T> { - fn into_value(self) -> Value { - match self { - Some(v) => v.into_value(), - None => Value::None, - } - } -} - -impl<T: FromValue> FromValue for Option<T> { - fn from_value(value: Value) -> StrResult<Self> { - match value { - Value::None => Ok(None), - v if T::castable(&v) => Ok(Some(T::from_value(v)?)), - _ => Err(Self::error(&value)), - } - } -} diff --git a/src/eval/ops.rs b/src/eval/ops.rs deleted file mode 100644 index 0880a87e..00000000 --- a/src/eval/ops.rs +++ /dev/null @@ -1,429 +0,0 @@ -//! Operations on values. - -use std::cmp::Ordering; -use std::fmt::Debug; - -use ecow::eco_format; - -use super::{format_str, Regex, Value}; -use crate::diag::{bail, StrResult}; -use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel, Smart}; -use Value::*; - -/// Bail with a type mismatch error. -macro_rules! mismatch { - ($fmt:expr, $($value:expr),* $(,)?) => { - return Err(eco_format!($fmt, $($value.type_name()),*)) - }; -} - -/// Join a value with another value. -pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (a, None) => a, - (None, b) => b, - (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")), - (Str(a), Str(b)) => Str(a + b), - (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")), - (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), - (Content(a), Content(b)) => Content(a + b), - (Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())), - (Content(a), Str(b)) => Content(a + item!(text)(b.into())), - (Str(a), Content(b)) => Content(item!(text)(a.into()) + b), - (Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b), - (Array(a), Array(b)) => Array(a + b), - (Dict(a), Dict(b)) => Dict(a + b), - (a, b) => mismatch!("cannot join {} with {}", a, b), - }) -} - -/// Apply the unary plus operator to a value. -pub fn pos(value: Value) -> StrResult<Value> { - Ok(match value { - Int(v) => Int(v), - Float(v) => Float(v), - Length(v) => Length(v), - Angle(v) => Angle(v), - Ratio(v) => Ratio(v), - Relative(v) => Relative(v), - Fraction(v) => Fraction(v), - v => mismatch!("cannot apply '+' to {}", v), - }) -} - -/// Compute the negation of a value. -pub fn neg(value: Value) -> StrResult<Value> { - Ok(match value { - Int(v) => Int(v.checked_neg().ok_or("value is too large")?), - Float(v) => Float(-v), - Length(v) => Length(-v), - Angle(v) => Angle(-v), - Ratio(v) => Ratio(-v), - Relative(v) => Relative(-v), - Fraction(v) => Fraction(-v), - v => mismatch!("cannot apply '-' to {}", v), - }) -} - -/// Compute the sum of two values. -pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (a, None) => a, - (None, b) => b, - - (Int(a), Int(b)) => Int(a.checked_add(b).ok_or("value is too large")?), - (Int(a), Float(b)) => Float(a as f64 + b), - (Float(a), Int(b)) => Float(a + b as f64), - (Float(a), Float(b)) => Float(a + b), - - (Angle(a), Angle(b)) => Angle(a + b), - - (Length(a), Length(b)) => Length(a + b), - (Length(a), Ratio(b)) => Relative(b + a), - (Length(a), Relative(b)) => Relative(b + a), - - (Ratio(a), Length(b)) => Relative(a + b), - (Ratio(a), Ratio(b)) => Ratio(a + b), - (Ratio(a), Relative(b)) => Relative(b + a), - - (Relative(a), Length(b)) => Relative(a + b), - (Relative(a), Ratio(b)) => Relative(a + b), - (Relative(a), Relative(b)) => Relative(a + b), - - (Fraction(a), Fraction(b)) => Fraction(a + b), - - (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")), - (Str(a), Str(b)) => Str(a + b), - (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")), - (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), - (Content(a), Content(b)) => Content(a + b), - (Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())), - (Content(a), Str(b)) => Content(a + item!(text)(b.into())), - (Str(a), Content(b)) => Content(item!(text)(a.into()) + b), - (Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b), - - (Array(a), Array(b)) => Array(a + b), - (Dict(a), Dict(b)) => Dict(a + b), - - (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => { - Value::dynamic(PartialStroke { - paint: Smart::Custom(color.into()), - thickness: Smart::Custom(thickness), - ..PartialStroke::default() - }) - } - - (Dyn(a), Dyn(b)) => { - // 1D alignments can be summed into 2D alignments. - if let (Some(&a), Some(&b)) = - (a.downcast::<GenAlign>(), b.downcast::<GenAlign>()) - { - if a.axis() == b.axis() { - return Err(eco_format!("cannot add two {:?} alignments", a.axis())); - } - - return Ok(Value::dynamic(match a.axis() { - Axis::X => Axes { x: a, y: b }, - Axis::Y => Axes { x: b, y: a }, - })); - }; - - mismatch!("cannot add {} and {}", a, b); - } - - (a, b) => mismatch!("cannot add {} and {}", a, b), - }) -} - -/// Compute the difference of two values. -pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (Int(a), Int(b)) => Int(a.checked_sub(b).ok_or("value is too large")?), - (Int(a), Float(b)) => Float(a as f64 - b), - (Float(a), Int(b)) => Float(a - b as f64), - (Float(a), Float(b)) => Float(a - b), - - (Angle(a), Angle(b)) => Angle(a - b), - - (Length(a), Length(b)) => Length(a - b), - (Length(a), Ratio(b)) => Relative(-b + a), - (Length(a), Relative(b)) => Relative(-b + a), - - (Ratio(a), Length(b)) => Relative(a + -b), - (Ratio(a), Ratio(b)) => Ratio(a - b), - (Ratio(a), Relative(b)) => Relative(-b + a), - - (Relative(a), Length(b)) => Relative(a + -b), - (Relative(a), Ratio(b)) => Relative(a + -b), - (Relative(a), Relative(b)) => Relative(a - b), - - (Fraction(a), Fraction(b)) => Fraction(a - b), - - (a, b) => mismatch!("cannot subtract {1} from {0}", a, b), - }) -} - -/// Compute the product of two values. -pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (Int(a), Int(b)) => Int(a.checked_mul(b).ok_or("value is too large")?), - (Int(a), Float(b)) => Float(a as f64 * b), - (Float(a), Int(b)) => Float(a * b as f64), - (Float(a), Float(b)) => Float(a * b), - - (Length(a), Int(b)) => Length(a * b as f64), - (Length(a), Float(b)) => Length(a * b), - (Length(a), Ratio(b)) => Length(a * b.get()), - (Int(a), Length(b)) => Length(b * a as f64), - (Float(a), Length(b)) => Length(b * a), - (Ratio(a), Length(b)) => Length(b * a.get()), - - (Angle(a), Int(b)) => Angle(a * b as f64), - (Angle(a), Float(b)) => Angle(a * b), - (Angle(a), Ratio(b)) => Angle(a * b.get()), - (Int(a), Angle(b)) => Angle(a as f64 * b), - (Float(a), Angle(b)) => Angle(a * b), - (Ratio(a), Angle(b)) => Angle(a.get() * b), - - (Ratio(a), Ratio(b)) => Ratio(a * b), - (Ratio(a), Int(b)) => Ratio(a * b as f64), - (Ratio(a), Float(b)) => Ratio(a * b), - (Int(a), Ratio(b)) => Ratio(a as f64 * b), - (Float(a), Ratio(b)) => Ratio(a * b), - - (Relative(a), Int(b)) => Relative(a * b as f64), - (Relative(a), Float(b)) => Relative(a * b), - (Relative(a), Ratio(b)) => Relative(a * b.get()), - (Int(a), Relative(b)) => Relative(a as f64 * b), - (Float(a), Relative(b)) => Relative(a * b), - (Ratio(a), Relative(b)) => Relative(a.get() * b), - - (Fraction(a), Int(b)) => Fraction(a * b as f64), - (Fraction(a), Float(b)) => Fraction(a * b), - (Fraction(a), Ratio(b)) => Fraction(a * b.get()), - (Int(a), Fraction(b)) => Fraction(a as f64 * b), - (Float(a), Fraction(b)) => Fraction(a * b), - (Ratio(a), Fraction(b)) => Fraction(a.get() * b), - - (Str(a), Int(b)) => Str(a.repeat(b)?), - (Int(a), Str(b)) => Str(b.repeat(a)?), - (Array(a), Int(b)) => Array(a.repeat(b)?), - (Int(a), Array(b)) => Array(b.repeat(a)?), - (Content(a), b @ Int(_)) => Content(a.repeat(b.cast()?)), - (a @ Int(_), Content(b)) => Content(b.repeat(a.cast()?)), - - (a, b) => mismatch!("cannot multiply {} with {}", a, b), - }) -} - -/// Compute the quotient of two values. -pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> { - if is_zero(&rhs) { - bail!("cannot divide by zero"); - } - - Ok(match (lhs, rhs) { - (Int(a), Int(b)) => Float(a as f64 / b as f64), - (Int(a), Float(b)) => Float(a as f64 / b), - (Float(a), Int(b)) => Float(a / b as f64), - (Float(a), Float(b)) => Float(a / b), - - (Length(a), Int(b)) => Length(a / b as f64), - (Length(a), Float(b)) => Length(a / b), - (Length(a), Length(b)) => Float(try_div_length(a, b)?), - (Length(a), Relative(b)) if b.rel.is_zero() => Float(try_div_length(a, b.abs)?), - - (Angle(a), Int(b)) => Angle(a / b as f64), - (Angle(a), Float(b)) => Angle(a / b), - (Angle(a), Angle(b)) => Float(a / b), - - (Ratio(a), Int(b)) => Ratio(a / b as f64), - (Ratio(a), Float(b)) => Ratio(a / b), - (Ratio(a), Ratio(b)) => Float(a / b), - (Ratio(a), Relative(b)) if b.abs.is_zero() => Float(a / b.rel), - - (Relative(a), Int(b)) => Relative(a / b as f64), - (Relative(a), Float(b)) => Relative(a / b), - (Relative(a), Length(b)) if a.rel.is_zero() => Float(try_div_length(a.abs, b)?), - (Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b), - (Relative(a), Relative(b)) => Float(try_div_relative(a, b)?), - - (Fraction(a), Int(b)) => Fraction(a / b as f64), - (Fraction(a), Float(b)) => Fraction(a / b), - (Fraction(a), Fraction(b)) => Float(a / b), - - (a, b) => mismatch!("cannot divide {} by {}", a, b), - }) -} - -/// Whether a value is a numeric zero. -fn is_zero(v: &Value) -> bool { - match *v { - Int(v) => v == 0, - Float(v) => v == 0.0, - Length(v) => v.is_zero(), - Angle(v) => v.is_zero(), - Ratio(v) => v.is_zero(), - Relative(v) => v.is_zero(), - Fraction(v) => v.is_zero(), - _ => false, - } -} - -/// Try to divide two lengths. -fn try_div_length(a: Length, b: Length) -> StrResult<f64> { - a.try_div(b).ok_or_else(|| "cannot divide these two lengths".into()) -} - -/// Try to divide two relative lengths. -fn try_div_relative(a: Rel<Length>, b: Rel<Length>) -> StrResult<f64> { - a.try_div(b) - .ok_or_else(|| "cannot divide these two relative lengths".into()) -} - -/// Compute the logical "not" of a value. -pub fn not(value: Value) -> StrResult<Value> { - match value { - Bool(b) => Ok(Bool(!b)), - v => mismatch!("cannot apply 'not' to {}", v), - } -} - -/// Compute the logical "and" of two values. -pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> { - match (lhs, rhs) { - (Bool(a), Bool(b)) => Ok(Bool(a && b)), - (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b), - } -} - -/// Compute the logical "or" of two values. -pub fn or(lhs: Value, rhs: Value) -> StrResult<Value> { - match (lhs, rhs) { - (Bool(a), Bool(b)) => Ok(Bool(a || b)), - (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b), - } -} - -/// Compute whether two values are equal. -pub fn eq(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(Bool(equal(&lhs, &rhs))) -} - -/// Compute whether two values are unequal. -pub fn neq(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(Bool(!equal(&lhs, &rhs))) -} - -macro_rules! comparison { - ($name:ident, $op:tt, $($pat:tt)*) => { - /// Compute how a value compares with another value. - pub fn $name(lhs: Value, rhs: Value) -> StrResult<Value> { - let ordering = compare(&lhs, &rhs)?; - Ok(Bool(matches!(ordering, $($pat)*))) - } - }; -} - -comparison!(lt, "<", Ordering::Less); -comparison!(leq, "<=", Ordering::Less | Ordering::Equal); -comparison!(gt, ">", Ordering::Greater); -comparison!(geq, ">=", Ordering::Greater | Ordering::Equal); - -/// Determine whether two values are equal. -pub fn equal(lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - // Compare reflexively. - (None, None) => true, - (Auto, Auto) => true, - (Bool(a), Bool(b)) => a == b, - (Int(a), Int(b)) => a == b, - (Float(a), Float(b)) => a == b, - (Length(a), Length(b)) => a == b, - (Angle(a), Angle(b)) => a == b, - (Ratio(a), Ratio(b)) => a == b, - (Relative(a), Relative(b)) => a == b, - (Fraction(a), Fraction(b)) => a == b, - (Color(a), Color(b)) => a == b, - (Symbol(a), Symbol(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, - (Func(a), Func(b)) => a == b, - (Args(a), Args(b)) => a == b, - (Module(a), Module(b)) => a == b, - (Dyn(a), Dyn(b)) => a == b, - - // Some technically different things should compare equal. - (&Int(a), &Float(b)) => a as f64 == b, - (&Float(a), &Int(b)) => a == b as f64, - (&Length(a), &Relative(b)) => a == b.abs && b.rel.is_zero(), - (&Ratio(a), &Relative(b)) => a == b.rel && b.abs.is_zero(), - (&Relative(a), &Length(b)) => a.abs == b && a.rel.is_zero(), - (&Relative(a), &Ratio(b)) => a.rel == b && a.abs.is_zero(), - - _ => false, - } -} - -/// Compare two values. -pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> { - Ok(match (lhs, rhs) { - (Bool(a), Bool(b)) => a.cmp(b), - (Int(a), Int(b)) => a.cmp(b), - (Float(a), Float(b)) => try_cmp_values(a, b)?, - (Length(a), Length(b)) => try_cmp_values(a, b)?, - (Angle(a), Angle(b)) => a.cmp(b), - (Ratio(a), Ratio(b)) => a.cmp(b), - (Relative(a), Relative(b)) => try_cmp_values(a, b)?, - (Fraction(a), Fraction(b)) => a.cmp(b), - (Str(a), Str(b)) => a.cmp(b), - - // Some technically different things should be comparable. - (Int(a), Float(b)) => try_cmp_values(&(*a as f64), b)?, - (Float(a), Int(b)) => try_cmp_values(a, &(*b as f64))?, - (Length(a), Relative(b)) if b.rel.is_zero() => try_cmp_values(a, &b.abs)?, - (Ratio(a), Relative(b)) if b.abs.is_zero() => a.cmp(&b.rel), - (Relative(a), Length(b)) if a.rel.is_zero() => try_cmp_values(&a.abs, b)?, - (Relative(a), Ratio(b)) if a.abs.is_zero() => a.rel.cmp(b), - - _ => mismatch!("cannot compare {} and {}", lhs, rhs), - }) -} - -/// Try to compare two values. -fn try_cmp_values<T: PartialOrd + Debug>(a: &T, b: &T) -> StrResult<Ordering> { - a.partial_cmp(b) - .ok_or_else(|| eco_format!("cannot compare {:?} with {:?}", a, b)) -} - -/// Test whether one value is "in" another one. -pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> { - if let Some(b) = contains(&lhs, &rhs) { - Ok(Bool(b)) - } else { - mismatch!("cannot apply 'in' to {} and {}", lhs, rhs) - } -} - -/// Test whether one value is "not in" another one. -pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> { - if let Some(b) = contains(&lhs, &rhs) { - Ok(Bool(!b)) - } else { - mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs) - } -} - -/// Test for containment. -pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> { - match (lhs, rhs) { - (Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())), - (Dyn(a), Str(b)) => a.downcast::<Regex>().map(|regex| regex.is_match(b)), - (Str(a), Dict(b)) => Some(b.contains(a)), - (a, Array(b)) => Some(b.contains(a)), - _ => Option::None, - } -} diff --git a/src/eval/scope.rs b/src/eval/scope.rs deleted file mode 100644 index f3e13715..00000000 --- a/src/eval/scope.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; - -use ecow::{eco_format, EcoString}; - -use super::{IntoValue, Library, Value}; -use crate::diag::{bail, StrResult}; - -/// A stack of scopes. -#[derive(Debug, Default, Clone)] -pub struct Scopes<'a> { - /// The active scope. - pub top: Scope, - /// The stack of lower scopes. - pub scopes: Vec<Scope>, - /// The standard library. - pub base: Option<&'a Library>, -} - -impl<'a> Scopes<'a> { - /// Create a new, empty hierarchy of scopes. - pub fn new(base: Option<&'a Library>) -> Self { - Self { top: Scope::new(), scopes: vec![], base } - } - - /// Enter a new scope. - pub fn enter(&mut self) { - self.scopes.push(std::mem::take(&mut self.top)); - } - - /// Exit the topmost scope. - /// - /// This panics if no scope was entered. - pub fn exit(&mut self) { - self.top = self.scopes.pop().expect("no pushed scope"); - } - - /// Try to access a variable immutably. - pub fn get(&self, var: &str) -> StrResult<&Value> { - std::iter::once(&self.top) - .chain(self.scopes.iter().rev()) - .chain(self.base.map(|base| base.global.scope())) - .find_map(|scope| scope.get(var)) - .ok_or_else(|| unknown_variable(var)) - } - - /// Try to access a variable immutably in math. - pub fn get_in_math(&self, var: &str) -> StrResult<&Value> { - std::iter::once(&self.top) - .chain(self.scopes.iter().rev()) - .chain(self.base.map(|base| base.math.scope())) - .find_map(|scope| scope.get(var)) - .ok_or_else(|| eco_format!("unknown variable: {}", var)) - } - - /// Try to access a variable mutably. - pub fn get_mut(&mut self, var: &str) -> StrResult<&mut Value> { - std::iter::once(&mut self.top) - .chain(&mut self.scopes.iter_mut().rev()) - .find_map(|scope| scope.get_mut(var)) - .ok_or_else(|| { - match self.base.and_then(|base| base.global.scope().get(var)) { - Some(_) => eco_format!("cannot mutate a constant: {}", var), - _ => unknown_variable(var), - } - })? - } -} - -/// The error message when a variable is not found. -#[cold] -fn unknown_variable(var: &str) -> EcoString { - if var.contains('-') { - eco_format!("unknown variable: {} - if you meant to use subtraction, try adding spaces around the minus sign.", var) - } else { - eco_format!("unknown variable: {}", var) - } -} - -/// A map from binding names to values. -#[derive(Default, Clone, Hash)] -pub struct Scope(BTreeMap<EcoString, Slot>, bool); - -impl Scope { - /// Create a new empty scope. - pub fn new() -> Self { - Self(BTreeMap::new(), false) - } - - /// Create a new scope with duplication prevention. - pub fn deduplicating() -> Self { - Self(BTreeMap::new(), true) - } - - /// Bind a value to a name. - #[track_caller] - pub fn define(&mut self, name: impl Into<EcoString>, value: impl IntoValue) { - let name = name.into(); - - #[cfg(debug_assertions)] - if self.1 && self.0.contains_key(&name) { - panic!("duplicate definition: {name}"); - } - - self.0.insert(name, Slot::new(value.into_value(), Kind::Normal)); - } - - /// Define a captured, immutable binding. - pub fn define_captured(&mut self, var: impl Into<EcoString>, value: impl IntoValue) { - self.0 - .insert(var.into(), Slot::new(value.into_value(), Kind::Captured)); - } - - /// Try to access a variable immutably. - pub fn get(&self, var: &str) -> Option<&Value> { - self.0.get(var).map(Slot::read) - } - - /// Try to access a variable mutably. - pub fn get_mut(&mut self, var: &str) -> Option<StrResult<&mut Value>> { - self.0.get_mut(var).map(Slot::write) - } - - /// Iterate over all definitions. - pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Value)> { - self.0.iter().map(|(k, v)| (k, v.read())) - } -} - -impl Debug for Scope { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Scope ")?; - f.debug_map() - .entries(self.0.iter().map(|(k, v)| (k, v.read()))) - .finish() - } -} - -/// A slot where a value is stored. -#[derive(Clone, Hash)] -struct Slot { - /// The stored value. - value: Value, - /// The kind of slot, determines how the value can be accessed. - kind: Kind, -} - -/// The different kinds of slots. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -enum Kind { - /// A normal, mutable binding. - Normal, - /// A captured copy of another variable. - Captured, -} - -impl Slot { - /// Create a new slot. - fn new(value: Value, kind: Kind) -> Self { - Self { value, kind } - } - - /// Read the value. - fn read(&self) -> &Value { - &self.value - } - - /// Try to write to the value. - fn write(&mut self) -> StrResult<&mut Value> { - match self.kind { - Kind::Normal => Ok(&mut self.value), - Kind::Captured => { - bail!("variables from outside the function are read-only and cannot be modified") - } - } - } -} diff --git a/src/eval/str.rs b/src/eval/str.rs deleted file mode 100644 index f5e5ab00..00000000 --- a/src/eval/str.rs +++ /dev/null @@ -1,620 +0,0 @@ -use std::borrow::{Borrow, Cow}; -use std::fmt::{self, Debug, Display, Formatter, Write}; -use std::hash::{Hash, Hasher}; -use std::ops::{Add, AddAssign, Deref, Range}; - -use ecow::EcoString; -use unicode_segmentation::UnicodeSegmentation; - -use super::{cast, dict, Args, Array, Dict, Func, IntoValue, Value, Vm}; -use crate::diag::{bail, At, SourceResult, StrResult}; -use crate::geom::GenAlign; - -/// Create a new [`Str`] from a format string. -#[macro_export] -#[doc(hidden)] -macro_rules! __format_str { - ($($tts:tt)*) => {{ - $crate::eval::Str::from($crate::eval::eco_format!($($tts)*)) - }}; -} - -#[doc(inline)] -pub use crate::__format_str as format_str; -#[doc(hidden)] -pub use ecow::eco_format; - -/// An immutable reference counted string. -#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Str(EcoString); - -impl Str { - /// Create a new, empty string. - pub fn new() -> Self { - Self(EcoString::new()) - } - - /// Return `true` if the length is 0. - pub fn is_empty(&self) -> bool { - self.0.len() == 0 - } - - /// The length of the string in bytes. - pub fn len(&self) -> usize { - self.0.len() - } - - /// A string slice containing the entire string. - pub fn as_str(&self) -> &str { - self - } - - /// Extract the first grapheme cluster. - pub fn first(&self) -> StrResult<Self> { - self.0 - .graphemes(true) - .next() - .map(Into::into) - .ok_or_else(string_is_empty) - } - - /// Extract the last grapheme cluster. - pub fn last(&self) -> StrResult<Self> { - self.0 - .graphemes(true) - .next_back() - .map(Into::into) - .ok_or_else(string_is_empty) - } - - /// Extract the grapheme cluster at the given index. - pub fn at<'a>(&'a self, index: i64, default: Option<&'a str>) -> StrResult<Self> { - let len = self.len(); - let grapheme = self - .locate_opt(index)? - .and_then(|i| self.0[i..].graphemes(true).next()) - .or(default) - .ok_or_else(|| no_default_and_out_of_bounds(index, len))?; - Ok(grapheme.into()) - } - - /// Extract a contiguous substring. - pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { - let start = self.locate(start)?; - let end = self.locate(end.unwrap_or(self.len() as i64))?.max(start); - Ok(self.0[start..end].into()) - } - - /// The grapheme clusters the string consists of. - pub fn clusters(&self) -> Array { - self.as_str().graphemes(true).map(|s| Value::Str(s.into())).collect() - } - - /// The codepoints the string consists of. - pub fn codepoints(&self) -> Array { - self.chars().map(|c| Value::Str(c.into())).collect() - } - - /// Whether the given pattern exists in this string. - pub fn contains(&self, pattern: StrPattern) -> bool { - match pattern { - StrPattern::Str(pat) => self.0.contains(pat.as_str()), - StrPattern::Regex(re) => re.is_match(self), - } - } - - /// Whether this string begins with the given pattern. - pub fn starts_with(&self, pattern: StrPattern) -> bool { - match pattern { - StrPattern::Str(pat) => self.0.starts_with(pat.as_str()), - StrPattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0), - } - } - - /// Whether this string ends with the given pattern. - pub fn ends_with(&self, pattern: StrPattern) -> bool { - match pattern { - StrPattern::Str(pat) => self.0.ends_with(pat.as_str()), - StrPattern::Regex(re) => { - re.find_iter(self).last().map_or(false, |m| m.end() == self.0.len()) - } - } - } - - /// The text of the pattern's first match in this string. - pub fn find(&self, pattern: StrPattern) -> Option<Self> { - match pattern { - StrPattern::Str(pat) => self.0.contains(pat.as_str()).then_some(pat), - StrPattern::Regex(re) => re.find(self).map(|m| m.as_str().into()), - } - } - - /// The position of the pattern's first match in this string. - pub fn position(&self, pattern: StrPattern) -> Option<i64> { - match pattern { - StrPattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64), - StrPattern::Regex(re) => re.find(self).map(|m| m.start() as i64), - } - } - - /// The start and, text and capture groups (if any) of the first match of - /// the pattern in this string. - pub fn match_(&self, pattern: StrPattern) -> Option<Dict> { - match pattern { - StrPattern::Str(pat) => { - self.0.match_indices(pat.as_str()).next().map(match_to_dict) - } - StrPattern::Regex(re) => re.captures(self).map(captures_to_dict), - } - } - - /// The start, end, text and capture groups (if any) of all matches of the - /// pattern in this string. - pub fn matches(&self, pattern: StrPattern) -> Array { - match pattern { - StrPattern::Str(pat) => self - .0 - .match_indices(pat.as_str()) - .map(match_to_dict) - .map(Value::Dict) - .collect(), - StrPattern::Regex(re) => re - .captures_iter(self) - .map(captures_to_dict) - .map(Value::Dict) - .collect(), - } - } - - /// Split this string at whitespace or a specific pattern. - pub fn split(&self, pattern: Option<StrPattern>) -> Array { - let s = self.as_str(); - match pattern { - None => s.split_whitespace().map(|v| Value::Str(v.into())).collect(), - Some(StrPattern::Str(pat)) => { - s.split(pat.as_str()).map(|v| Value::Str(v.into())).collect() - } - Some(StrPattern::Regex(re)) => { - re.split(s).map(|v| Value::Str(v.into())).collect() - } - } - } - - /// Trim either whitespace or the given pattern at both or just one side of - /// the string. If `repeat` is true, the pattern is trimmed repeatedly - /// instead of just once. Repeat must only be given in combination with a - /// pattern. - pub fn trim( - &self, - pattern: Option<StrPattern>, - at: Option<StrSide>, - repeat: bool, - ) -> Self { - let mut start = matches!(at, Some(StrSide::Start) | None); - let end = matches!(at, Some(StrSide::End) | None); - - let trimmed = match pattern { - None => match at { - None => self.0.trim(), - Some(StrSide::Start) => self.0.trim_start(), - Some(StrSide::End) => self.0.trim_end(), - }, - Some(StrPattern::Str(pat)) => { - let pat = pat.as_str(); - let mut s = self.as_str(); - if repeat { - if start { - s = s.trim_start_matches(pat); - } - if end { - s = s.trim_end_matches(pat); - } - } else { - if start { - s = s.strip_prefix(pat).unwrap_or(s); - } - if end { - s = s.strip_suffix(pat).unwrap_or(s); - } - } - s - } - Some(StrPattern::Regex(re)) => { - let s = self.as_str(); - let mut last = 0; - let mut range = 0..s.len(); - - for m in re.find_iter(s) { - // Does this match follow directly after the last one? - let consecutive = last == m.start(); - - // As long as we're consecutive and still trimming at the - // start, trim. - start &= consecutive; - if start { - range.start = m.end(); - start &= repeat; - } - - // Reset end trim if we aren't consecutive anymore or aren't - // repeating. - if end && (!consecutive || !repeat) { - range.end = m.start(); - } - - last = m.end(); - } - - // Is the last match directly at the end? - if last < s.len() { - range.end = s.len(); - } - - &s[range.start..range.start.max(range.end)] - } - }; - - trimmed.into() - } - - /// Replace at most `count` occurrences of the given pattern with a - /// replacement string or function (beginning from the start). If no count - /// is given, all occurrences are replaced. - pub fn replace( - &self, - vm: &mut Vm, - pattern: StrPattern, - with: Replacement, - count: Option<usize>, - ) -> SourceResult<Self> { - // Heuristic: Assume the new string is about the same length as - // the current string. - let mut output = EcoString::with_capacity(self.as_str().len()); - - // Replace one match of a pattern with the replacement. - let mut last_match = 0; - let mut handle_match = |range: Range<usize>, dict: Dict| -> SourceResult<()> { - // Push everything until the match. - output.push_str(&self[last_match..range.start]); - last_match = range.end; - - // Determine and push the replacement. - match &with { - Replacement::Str(s) => output.push_str(s), - Replacement::Func(func) => { - let args = Args::new(func.span(), [dict]); - let piece = func.call_vm(vm, args)?.cast::<Str>().at(func.span())?; - output.push_str(&piece); - } - } - - Ok(()) - }; - - // Iterate over the matches of the `pattern`. - let count = count.unwrap_or(usize::MAX); - match &pattern { - StrPattern::Str(pat) => { - for m in self.match_indices(pat.as_str()).take(count) { - let (start, text) = m; - handle_match(start..start + text.len(), match_to_dict(m))?; - } - } - StrPattern::Regex(re) => { - for caps in re.captures_iter(self).take(count) { - // Extract the entire match over all capture groups. - let m = caps.get(0).unwrap(); - handle_match(m.start()..m.end(), captures_to_dict(caps))?; - } - } - } - - // Push the remainder. - output.push_str(&self[last_match..]); - Ok(output.into()) - } - - /// Repeat the string a number of times. - pub fn repeat(&self, n: i64) -> StrResult<Self> { - let n = usize::try_from(n) - .ok() - .and_then(|n| self.0.len().checked_mul(n).map(|_| n)) - .ok_or_else(|| format!("cannot repeat this string {} times", n))?; - - Ok(Self(self.0.repeat(n))) - } - - /// Resolve an index, if it is within bounds. - /// Errors on invalid char boundaries. - fn locate_opt(&self, index: i64) -> StrResult<Option<usize>> { - let wrapped = - if index >= 0 { Some(index) } else { (self.len() as i64).checked_add(index) }; - - let resolved = wrapped - .and_then(|v| usize::try_from(v).ok()) - .filter(|&v| v <= self.0.len()); - - if resolved.map_or(false, |i| !self.0.is_char_boundary(i)) { - return Err(not_a_char_boundary(index)); - } - - Ok(resolved) - } - - /// Resolve an index or throw an out of bounds error. - fn locate(&self, index: i64) -> StrResult<usize> { - self.locate_opt(index)? - .ok_or_else(|| out_of_bounds(index, self.len())) - } -} - -/// The out of bounds access error message. -#[cold] -fn out_of_bounds(index: i64, len: usize) -> EcoString { - eco_format!("string index out of bounds (index: {}, len: {})", index, len) -} - -/// The out of bounds access error message when no default value was given. -#[cold] -fn no_default_and_out_of_bounds(index: i64, len: usize) -> EcoString { - eco_format!("no default value was specified and string index out of bounds (index: {}, len: {})", index, len) -} - -/// The char boundary access error message. -#[cold] -fn not_a_char_boundary(index: i64) -> EcoString { - eco_format!("string index {} is not a character boundary", index) -} - -/// The error message when the string is empty. -#[cold] -fn string_is_empty() -> EcoString { - "string is empty".into() -} - -/// Convert an item of std's `match_indices` to a dictionary. -fn match_to_dict((start, text): (usize, &str)) -> Dict { - dict! { - "start" => start, - "end" => start + text.len(), - "text" => text, - "captures" => Array::new(), - } -} - -/// Convert regex captures to a dictionary. -fn captures_to_dict(cap: regex::Captures) -> Dict { - let m = cap.get(0).expect("missing first match"); - dict! { - "start" => m.start(), - "end" => m.end(), - "text" => m.as_str(), - "captures" => cap.iter() - .skip(1) - .map(|opt| opt.map_or(Value::None, |m| m.as_str().into_value())) - .collect::<Array>(), - } -} - -impl Deref for Str { - type Target = str; - - fn deref(&self) -> &str { - &self.0 - } -} - -impl Display for Str { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(self) - } -} - -impl Debug for Str { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('"')?; - for c in self.chars() { - match c { - '\0' => f.write_str("\\u{0}")?, - '\'' => f.write_str("'")?, - '"' => f.write_str(r#"\""#)?, - _ => Display::fmt(&c.escape_debug(), f)?, - } - } - f.write_char('"') - } -} - -impl Add for Str { - type Output = Self; - - fn add(mut self, rhs: Self) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Str { - fn add_assign(&mut self, rhs: Self) { - self.0.push_str(rhs.as_str()); - } -} - -impl AsRef<str> for Str { - fn as_ref(&self) -> &str { - self - } -} - -impl Borrow<str> for Str { - fn borrow(&self) -> &str { - self - } -} - -impl From<char> for Str { - fn from(c: char) -> Self { - Self(c.into()) - } -} - -impl From<&str> for Str { - fn from(s: &str) -> Self { - Self(s.into()) - } -} - -impl From<EcoString> for Str { - fn from(s: EcoString) -> Self { - Self(s) - } -} - -impl From<String> for Str { - fn from(s: String) -> Self { - Self(s.into()) - } -} - -impl From<Cow<'_, str>> for Str { - fn from(s: Cow<str>) -> Self { - Self(s.into()) - } -} - -impl FromIterator<char> for Str { - fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self { - Self(iter.into_iter().collect()) - } -} - -impl From<Str> for EcoString { - fn from(str: Str) -> Self { - str.0 - } -} - -impl From<Str> for String { - fn from(s: Str) -> Self { - s.0.into() - } -} - -cast! { - char, - self => Value::Str(self.into()), - string: Str => { - let mut chars = string.chars(); - match (chars.next(), chars.next()) { - (Some(c), None) => c, - _ => bail!("expected exactly one character"), - } - }, -} - -cast! { - &str, - self => Value::Str(self.into()), -} - -cast! { - EcoString, - self => Value::Str(self.into()), - v: Str => v.into(), -} - -cast! { - String, - self => Value::Str(self.into()), - v: Str => v.into(), -} - -/// A regular expression. -#[derive(Clone)] -pub struct Regex(regex::Regex); - -impl Regex { - /// Create a new regular expression. - pub fn new(re: &str) -> StrResult<Self> { - regex::Regex::new(re).map(Self).map_err(|err| eco_format!("{err}")) - } -} - -impl Deref for Regex { - type Target = regex::Regex; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Debug for Regex { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "regex({:?})", self.0.as_str()) - } -} - -impl PartialEq for Regex { - fn eq(&self, other: &Self) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Hash for Regex { - fn hash<H: Hasher>(&self, state: &mut H) { - self.0.as_str().hash(state); - } -} - -cast! { - type Regex: "regular expression", -} - -/// A pattern which can be searched for in a string. -#[derive(Debug, Clone)] -pub enum StrPattern { - /// Just a string. - Str(Str), - /// A regular expression. - Regex(Regex), -} - -cast! { - StrPattern, - text: Str => Self::Str(text), - regex: Regex => Self::Regex(regex), -} - -/// A side of a string. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum StrSide { - /// The logical start of the string, may be left or right depending on the - /// language. - Start, - /// The logical end of the string. - End, -} - -cast! { - StrSide, - align: GenAlign => match align { - GenAlign::Start => Self::Start, - GenAlign::End => Self::End, - _ => bail!("expected either `start` or `end`"), - }, -} - -/// A replacement for a matched [`Str`] -pub enum Replacement { - /// A string a match is replaced with. - Str(Str), - /// Function of type Dict -> Str (see `captures_to_dict` or `match_to_dict`) - /// whose output is inserted for the match. - Func(Func), -} - -cast! { - Replacement, - text: Str => Self::Str(text), - func: Func => Self::Func(func) -} diff --git a/src/eval/symbol.rs b/src/eval/symbol.rs deleted file mode 100644 index 0925202e..00000000 --- a/src/eval/symbol.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::cmp::Reverse; -use std::collections::BTreeSet; -use std::fmt::{self, Debug, Display, Formatter, Write}; -use std::sync::Arc; - -use ecow::EcoString; - -use crate::diag::{bail, StrResult}; - -/// A symbol, possibly with variants. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct Symbol(Repr); - -/// The internal representation. -#[derive(Clone, Eq, PartialEq, Hash)] -enum Repr { - Single(char), - Const(&'static [(&'static str, char)]), - Multi(Arc<(List, EcoString)>), -} - -/// A collection of symbols. -#[derive(Clone, Eq, PartialEq, Hash)] -enum List { - Static(&'static [(&'static str, char)]), - Runtime(Box<[(EcoString, char)]>), -} - -impl Symbol { - /// Create a new symbol from a single character. - pub const fn new(c: char) -> Self { - Self(Repr::Single(c)) - } - - /// Create a symbol with a static variant list. - #[track_caller] - pub const fn list(list: &'static [(&'static str, char)]) -> Self { - debug_assert!(!list.is_empty()); - Self(Repr::Const(list)) - } - - /// Create a symbol with a runtime variant list. - #[track_caller] - pub fn runtime(list: Box<[(EcoString, char)]>) -> Self { - debug_assert!(!list.is_empty()); - Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new())))) - } - - /// Get the symbol's text. - pub fn get(&self) -> char { - match &self.0 { - Repr::Single(c) => *c, - Repr::Const(_) => find(self.variants(), "").unwrap(), - Repr::Multi(arc) => find(self.variants(), &arc.1).unwrap(), - } - } - - /// Apply a modifier to the symbol. - pub fn modified(mut self, modifier: &str) -> StrResult<Self> { - if let Repr::Const(list) = self.0 { - self.0 = Repr::Multi(Arc::new((List::Static(list), EcoString::new()))); - } - - if let Repr::Multi(arc) = &mut self.0 { - let (list, modifiers) = Arc::make_mut(arc); - if !modifiers.is_empty() { - modifiers.push('.'); - } - modifiers.push_str(modifier); - if find(list.variants(), modifiers).is_some() { - return Ok(self); - } - } - - bail!("unknown symbol modifier") - } - - /// The characters that are covered by this symbol. - pub fn variants(&self) -> impl Iterator<Item = (&str, char)> { - match &self.0 { - Repr::Single(c) => Variants::Single(Some(*c).into_iter()), - Repr::Const(list) => Variants::Static(list.iter()), - Repr::Multi(arc) => arc.0.variants(), - } - } - - /// Possible modifiers. - pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ { - let mut set = BTreeSet::new(); - let modifiers = match &self.0 { - Repr::Multi(arc) => arc.1.as_str(), - _ => "", - }; - for modifier in self.variants().flat_map(|(name, _)| name.split('.')) { - if !modifier.is_empty() && !contained(modifiers, modifier) { - set.insert(modifier); - } - } - set.into_iter() - } - - /// Normalize an accent to a combining one. - pub fn combining_accent(c: char) -> Option<char> { - Some(match c { - '\u{0300}' | '`' => '\u{0300}', - '\u{0301}' | '´' => '\u{0301}', - '\u{0302}' | '^' | 'ˆ' => '\u{0302}', - '\u{0303}' | '~' | '∼' | '˜' => '\u{0303}', - '\u{0304}' | '¯' => '\u{0304}', - '\u{0305}' | '-' | '‾' | '−' => '\u{0305}', - '\u{0306}' | '˘' => '\u{0306}', - '\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}', - '\u{0308}' | '¨' => '\u{0308}', - '\u{20db}' => '\u{20db}', - '\u{20dc}' => '\u{20dc}', - '\u{030a}' | '∘' | '○' => '\u{030a}', - '\u{030b}' | '˝' => '\u{030b}', - '\u{030c}' | 'ˇ' => '\u{030c}', - '\u{20d6}' | '←' => '\u{20d6}', - '\u{20d7}' | '→' | '⟶' => '\u{20d7}', - _ => return None, - }) - } -} - -impl Debug for Symbol { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char(self.get()) - } -} - -impl Display for Symbol { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char(self.get()) - } -} - -impl List { - /// The characters that are covered by this list. - fn variants(&self) -> Variants<'_> { - match self { - List::Static(list) => Variants::Static(list.iter()), - List::Runtime(list) => Variants::Runtime(list.iter()), - } - } -} - -/// Iterator over variants. -enum Variants<'a> { - Single(std::option::IntoIter<char>), - Static(std::slice::Iter<'static, (&'static str, char)>), - Runtime(std::slice::Iter<'a, (EcoString, char)>), -} - -impl<'a> Iterator for Variants<'a> { - type Item = (&'a str, char); - - fn next(&mut self) -> Option<Self::Item> { - match self { - Self::Single(iter) => Some(("", iter.next()?)), - Self::Static(list) => list.next().copied(), - Self::Runtime(list) => list.next().map(|(s, c)| (s.as_str(), *c)), - } - } -} - -/// Find the best symbol from the list. -fn find<'a>( - variants: impl Iterator<Item = (&'a str, char)>, - modifiers: &str, -) -> Option<char> { - let mut best = None; - let mut best_score = None; - - // Find the best table entry with this name. - 'outer: for candidate in variants { - for modifier in parts(modifiers) { - if !contained(candidate.0, modifier) { - continue 'outer; - } - } - - let mut matching = 0; - let mut total = 0; - for modifier in parts(candidate.0) { - if contained(modifiers, modifier) { - matching += 1; - } - total += 1; - } - - let score = (matching, Reverse(total)); - if best_score.map_or(true, |b| score > b) { - best = Some(candidate.1); - best_score = Some(score); - } - } - - best -} - -/// Split a modifier list into its parts. -fn parts(modifiers: &str) -> impl Iterator<Item = &str> { - modifiers.split('.').filter(|s| !s.is_empty()) -} - -/// Whether the modifier string contains the modifier `m`. -fn contained(modifiers: &str, m: &str) -> bool { - parts(modifiers).any(|part| part == m) -} diff --git a/src/eval/value.rs b/src/eval/value.rs deleted file mode 100644 index b1782cab..00000000 --- a/src/eval/value.rs +++ /dev/null @@ -1,461 +0,0 @@ -use std::any::Any; -use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -use ecow::eco_format; -use siphasher::sip128::{Hasher128, SipHasher13}; - -use super::{ - cast, format_str, ops, Args, Array, CastInfo, Content, Dict, FromValue, Func, - IntoValue, Module, Reflect, Str, Symbol, -}; -use crate::diag::StrResult; -use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel}; -use crate::model::{Label, Styles}; -use crate::syntax::{ast, Span}; -use crate::util::Bytes; - -/// A computational value. -#[derive(Default, Clone)] -pub enum Value { - /// The value that indicates the absence of a meaningful value. - #[default] - None, - /// A value that indicates some smart default behaviour. - Auto, - /// A boolean: `true, false`. - Bool(bool), - /// An integer: `120`. - Int(i64), - /// A floating-point number: `1.2`, `10e-4`. - Float(f64), - /// A length: `12pt`, `3cm`, `1.5em`, `1em - 2pt`. - Length(Length), - /// An angle: `1.5rad`, `90deg`. - Angle(Angle), - /// A ratio: `50%`. - Ratio(Ratio), - /// A relative length, combination of a ratio and a length: `20% + 5cm`. - Relative(Rel<Length>), - /// A fraction: `1fr`. - Fraction(Fr), - /// A color value: `#f79143ff`. - Color(Color), - /// A symbol: `arrow.l`. - Symbol(Symbol), - /// A string: `"string"`. - Str(Str), - /// Raw bytes. - Bytes(Bytes), - /// A label: `<intro>`. - Label(Label), - /// A content value: `[*Hi* there]`. - Content(Content), - // Content styles. - Styles(Styles), - /// An array of values: `(1, "hi", 12cm)`. - Array(Array), - /// A dictionary value: `(a: 1, b: "hi")`. - Dict(Dict), - /// An executable function. - Func(Func), - /// Captured arguments to a function. - Args(Args), - /// A module. - Module(Module), - /// A dynamic value. - Dyn(Dynamic), -} - -impl Value { - /// Create a new dynamic value. - pub fn dynamic<T>(any: T) -> Self - where - T: Type + Debug + PartialEq + Hash + Sync + Send + 'static, - { - Self::Dyn(Dynamic::new(any)) - } - - /// Create a numeric value from a number with a unit. - pub fn numeric(pair: (f64, ast::Unit)) -> Self { - let (v, unit) = pair; - match unit { - ast::Unit::Length(unit) => Abs::with_unit(v, unit).into_value(), - ast::Unit::Angle(unit) => Angle::with_unit(v, unit).into_value(), - ast::Unit::Em => Em::new(v).into_value(), - ast::Unit::Fr => Fr::new(v).into_value(), - ast::Unit::Percent => Ratio::new(v / 100.0).into_value(), - } - } - - /// The name of the stored value's type. - pub fn type_name(&self) -> &'static str { - match self { - Self::None => "none", - Self::Auto => "auto", - Self::Bool(_) => bool::TYPE_NAME, - Self::Int(_) => i64::TYPE_NAME, - Self::Float(_) => f64::TYPE_NAME, - Self::Length(_) => Length::TYPE_NAME, - Self::Angle(_) => Angle::TYPE_NAME, - Self::Ratio(_) => Ratio::TYPE_NAME, - Self::Relative(_) => Rel::<Length>::TYPE_NAME, - Self::Fraction(_) => Fr::TYPE_NAME, - Self::Color(_) => Color::TYPE_NAME, - Self::Symbol(_) => Symbol::TYPE_NAME, - Self::Str(_) => Str::TYPE_NAME, - Self::Bytes(_) => Bytes::TYPE_NAME, - Self::Label(_) => Label::TYPE_NAME, - Self::Content(_) => Content::TYPE_NAME, - Self::Styles(_) => Styles::TYPE_NAME, - Self::Array(_) => Array::TYPE_NAME, - Self::Dict(_) => Dict::TYPE_NAME, - Self::Func(_) => Func::TYPE_NAME, - Self::Args(_) => Args::TYPE_NAME, - Self::Module(_) => Module::TYPE_NAME, - Self::Dyn(v) => v.type_name(), - } - } - - /// Try to cast the value into a specific type. - pub fn cast<T: FromValue>(self) -> StrResult<T> { - T::from_value(self) - } - - /// Try to access a field on the value. - pub fn field(&self, field: &str) -> StrResult<Value> { - match self { - Self::Symbol(symbol) => symbol.clone().modified(field).map(Self::Symbol), - Self::Dict(dict) => dict.at(field, None).cloned(), - Self::Content(content) => content.at(field, None), - Self::Module(module) => module.get(field).cloned(), - Self::Func(func) => func.get(field).cloned(), - v => Err(eco_format!("cannot access fields on type {}", v.type_name())), - } - } - - /// Return the debug representation of the value. - pub fn repr(&self) -> Str { - format_str!("{:?}", self) - } - - /// Attach a span to the value, if possible. - pub fn spanned(self, span: Span) -> Self { - match self { - Value::Content(v) => Value::Content(v.spanned(span)), - Value::Func(v) => Value::Func(v.spanned(span)), - v => v, - } - } - - /// Return the display representation of the value. - pub fn display(self) -> Content { - match self { - Self::None => Content::empty(), - Self::Int(v) => item!(text)(eco_format!("{}", v)), - Self::Float(v) => item!(text)(eco_format!("{}", v)), - Self::Str(v) => item!(text)(v.into()), - Self::Symbol(v) => item!(text)(v.get().into()), - Self::Content(v) => v, - Self::Func(_) => Content::empty(), - Self::Module(module) => module.content(), - _ => item!(raw)(self.repr().into(), Some("typc".into()), false), - } - } - - /// Try to extract documentation for the value. - pub fn docs(&self) -> Option<&'static str> { - match self { - Self::Func(func) => func.info().map(|info| info.docs), - _ => None, - } - } -} - -impl Debug for Value { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::None => f.pad("none"), - Self::Auto => f.pad("auto"), - Self::Bool(v) => Debug::fmt(v, f), - Self::Int(v) => Debug::fmt(v, f), - Self::Float(v) => Debug::fmt(v, f), - Self::Length(v) => Debug::fmt(v, f), - Self::Angle(v) => Debug::fmt(v, f), - Self::Ratio(v) => Debug::fmt(v, f), - Self::Relative(v) => Debug::fmt(v, f), - Self::Fraction(v) => Debug::fmt(v, f), - Self::Color(v) => Debug::fmt(v, f), - Self::Symbol(v) => Debug::fmt(v, f), - Self::Str(v) => Debug::fmt(v, f), - Self::Bytes(v) => Debug::fmt(v, f), - Self::Label(v) => Debug::fmt(v, f), - Self::Content(v) => Debug::fmt(v, f), - Self::Styles(v) => Debug::fmt(v, f), - Self::Array(v) => Debug::fmt(v, f), - Self::Dict(v) => Debug::fmt(v, f), - Self::Func(v) => Debug::fmt(v, f), - Self::Args(v) => Debug::fmt(v, f), - Self::Module(v) => Debug::fmt(v, f), - Self::Dyn(v) => Debug::fmt(v, f), - } - } -} - -impl PartialEq for Value { - fn eq(&self, other: &Self) -> bool { - ops::equal(self, other) - } -} - -impl PartialOrd for Value { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - ops::compare(self, other).ok() - } -} - -impl Hash for Value { - fn hash<H: Hasher>(&self, state: &mut H) { - std::mem::discriminant(self).hash(state); - match self { - Self::None => {} - Self::Auto => {} - Self::Bool(v) => v.hash(state), - Self::Int(v) => v.hash(state), - Self::Float(v) => v.to_bits().hash(state), - Self::Length(v) => v.hash(state), - Self::Angle(v) => v.hash(state), - Self::Ratio(v) => v.hash(state), - Self::Relative(v) => v.hash(state), - Self::Fraction(v) => v.hash(state), - Self::Color(v) => v.hash(state), - Self::Symbol(v) => v.hash(state), - Self::Str(v) => v.hash(state), - Self::Bytes(v) => v.hash(state), - Self::Label(v) => v.hash(state), - Self::Content(v) => v.hash(state), - Self::Styles(v) => v.hash(state), - Self::Array(v) => v.hash(state), - Self::Dict(v) => v.hash(state), - Self::Func(v) => v.hash(state), - Self::Args(v) => v.hash(state), - Self::Module(v) => v.hash(state), - Self::Dyn(v) => v.hash(state), - } - } -} - -/// A dynamic value. -#[derive(Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Dynamic(Arc<dyn Bounds>); - -impl Dynamic { - /// Create a new instance from any value that satisfies the required bounds. - pub fn new<T>(any: T) -> Self - where - T: Type + Debug + PartialEq + Hash + Sync + Send + 'static, - { - Self(Arc::new(any)) - } - - /// Whether the wrapped type is `T`. - pub fn is<T: Type + 'static>(&self) -> bool { - (*self.0).as_any().is::<T>() - } - - /// Try to downcast to a reference to a specific type. - pub fn downcast<T: Type + 'static>(&self) -> Option<&T> { - (*self.0).as_any().downcast_ref() - } - - /// The name of the stored value's type. - pub fn type_name(&self) -> &'static str { - self.0.dyn_type_name() - } -} - -impl Debug for Dynamic { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(&self.0, f) - } -} - -impl PartialEq for Dynamic { - fn eq(&self, other: &Self) -> bool { - self.0.dyn_eq(other) - } -} - -cast! { - Dynamic, - self => Value::Dyn(self), -} - -trait Bounds: Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn dyn_eq(&self, other: &Dynamic) -> bool; - fn dyn_type_name(&self) -> &'static str; - fn hash128(&self) -> u128; -} - -impl<T> Bounds for T -where - T: Type + Debug + PartialEq + Hash + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn dyn_eq(&self, other: &Dynamic) -> bool { - let Some(other) = other.downcast::<Self>() else { return false }; - self == other - } - - fn dyn_type_name(&self) -> &'static str { - T::TYPE_NAME - } - - #[tracing::instrument(skip_all)] - fn hash128(&self) -> u128 { - // Also hash the TypeId since values with different types but - // equal data should be different. - let mut state = SipHasher13::new(); - self.type_id().hash(&mut state); - self.hash(&mut state); - state.finish128().as_u128() - } -} - -impl Hash for dyn Bounds { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u128(self.hash128()); - } -} - -/// The type of a value. -pub trait Type { - /// The name of the type. - const TYPE_NAME: &'static str; -} - -/// Implement traits for primitives. -macro_rules! primitive { - ( - $ty:ty: $name:literal, $variant:ident - $(, $other:ident$(($binding:ident))? => $out:expr)* - ) => { - impl Type for $ty { - const TYPE_NAME: &'static str = $name; - } - - impl Reflect for $ty { - fn describe() -> CastInfo { - CastInfo::Type(Self::TYPE_NAME) - } - - fn castable(value: &Value) -> bool { - matches!(value, Value::$variant(_) - $(| primitive!(@$other $(($binding))?))*) - } - } - - impl IntoValue for $ty { - fn into_value(self) -> Value { - Value::$variant(self) - } - } - - impl FromValue for $ty { - fn from_value(value: Value) -> StrResult<Self> { - match value { - Value::$variant(v) => Ok(v), - $(Value::$other$(($binding))? => Ok($out),)* - v => Err(eco_format!( - "expected {}, found {}", - Self::TYPE_NAME, - v.type_name(), - )), - } - } - } - }; - - (@$other:ident($binding:ident)) => { Value::$other(_) }; - (@$other:ident) => { Value::$other }; -} - -primitive! { bool: "boolean", Bool } -primitive! { i64: "integer", Int } -primitive! { f64: "float", Float, Int(v) => v as f64 } -primitive! { Length: "length", Length } -primitive! { Angle: "angle", Angle } -primitive! { Ratio: "ratio", Ratio } -primitive! { Rel<Length>: "relative length", - Relative, - Length(v) => v.into(), - Ratio(v) => v.into() -} -primitive! { Fr: "fraction", Fraction } -primitive! { Color: "color", Color } -primitive! { Symbol: "symbol", Symbol } -primitive! { - Str: "string", - Str, - Symbol(symbol) => symbol.get().into() -} -primitive! { Bytes: "bytes", Bytes } -primitive! { Label: "label", Label } -primitive! { Content: "content", - Content, - None => Content::empty(), - Symbol(v) => item!(text)(v.get().into()), - Str(v) => item!(text)(v.into()) -} -primitive! { Styles: "styles", Styles } -primitive! { Array: "array", Array } -primitive! { Dict: "dictionary", Dict } -primitive! { Func: "function", Func } -primitive! { Args: "arguments", Args } -primitive! { Module: "module", Module } - -#[cfg(test)] -mod tests { - use super::*; - use crate::eval::{array, dict}; - use crate::geom::RgbaColor; - - #[track_caller] - fn test(value: impl IntoValue, exp: &str) { - assert_eq!(format!("{:?}", value.into_value()), exp); - } - - #[test] - fn test_value_debug() { - // Primitives. - test(Value::None, "none"); - test(false, "false"); - test(12i64, "12"); - test(3.24, "3.24"); - test(Abs::pt(5.5), "5.5pt"); - test(Angle::deg(90.0), "90deg"); - test(Ratio::one() / 2.0, "50%"); - test(Ratio::new(0.3) + Length::from(Abs::cm(2.0)), "30% + 56.69pt"); - test(Fr::one() * 7.55, "7.55fr"); - test(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "rgb(\"#010101\")"); - - // Collections. - test("hello", r#""hello""#); - test("\n", r#""\n""#); - test("\\", r#""\\""#); - test("\"", r#""\"""#); - test(array![], "()"); - test(array![Value::None], "(none,)"); - test(array![1, 2], "(1, 2)"); - test(dict![], "(:)"); - test(dict!["one" => 1], "(one: 1)"); - test(dict!["two" => false, "one" => 1], "(two: false, one: 1)"); - } -} diff --git a/src/export/mod.rs b/src/export/mod.rs deleted file mode 100644 index eb0731a9..00000000 --- a/src/export/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Exporting into external formats. - -mod pdf; -mod render; - -pub use self::pdf::pdf; -pub use self::render::render; diff --git a/src/export/pdf/font.rs b/src/export/pdf/font.rs deleted file mode 100644 index f0676d8f..00000000 --- a/src/export/pdf/font.rs +++ /dev/null @@ -1,204 +0,0 @@ -use std::collections::BTreeMap; - -use ecow::{eco_format, EcoString}; -use pdf_writer::types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap}; -use pdf_writer::{Filter, Finish, Name, Rect, Str}; -use ttf_parser::{name_id, GlyphId, Tag}; -use unicode_general_category::GeneralCategory; - -use super::{deflate, EmExt, PdfContext, RefExt}; -use crate::font::Font; -use crate::util::{Bytes, SliceExt}; - -const CMAP_NAME: Name = Name(b"Custom"); -const SYSTEM_INFO: SystemInfo = SystemInfo { - registry: Str(b"Adobe"), - ordering: Str(b"Identity"), - supplement: 0, -}; - -/// Embed all used fonts into the PDF. -#[tracing::instrument(skip_all)] -pub fn write_fonts(ctx: &mut PdfContext) { - for font in ctx.font_map.items() { - let type0_ref = ctx.alloc.bump(); - let cid_ref = ctx.alloc.bump(); - let descriptor_ref = ctx.alloc.bump(); - let cmap_ref = ctx.alloc.bump(); - let data_ref = ctx.alloc.bump(); - ctx.font_refs.push(type0_ref); - - let glyph_set = ctx.glyph_sets.get_mut(font).unwrap(); - let metrics = font.metrics(); - let ttf = font.ttf(); - - let postscript_name = font - .find_name(name_id::POST_SCRIPT_NAME) - .unwrap_or_else(|| "unknown".to_string()); - - let base_font = eco_format!("ABCDEF+{}", postscript_name); - let base_font = Name(base_font.as_bytes()); - - // Write the base font object referencing the CID font. - ctx.writer - .type0_font(type0_ref) - .base_font(base_font) - .encoding_predefined(Name(b"Identity-H")) - .descendant_font(cid_ref) - .to_unicode(cmap_ref); - - // Check for the presence of CFF outlines to select the correct - // CID-Font subtype. - let subtype = match ttf - .raw_face() - .table(Tag::from_bytes(b"CFF ")) - .or(ttf.raw_face().table(Tag::from_bytes(b"CFF2"))) - { - Some(_) => CidFontType::Type0, - None => CidFontType::Type2, - }; - - // Write the CID font referencing the font descriptor. - let mut cid = ctx.writer.cid_font(cid_ref); - cid.subtype(subtype); - cid.base_font(base_font); - cid.system_info(SYSTEM_INFO); - cid.font_descriptor(descriptor_ref); - cid.default_width(0.0); - - if subtype == CidFontType::Type2 { - cid.cid_to_gid_map_predefined(Name(b"Identity")); - } - - // Extract the widths of all glyphs. - let num_glyphs = ttf.number_of_glyphs(); - let mut widths = vec![0.0; num_glyphs as usize]; - for &g in glyph_set.keys() { - let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0); - widths[g as usize] = font.to_em(x).to_font_units(); - } - - // Write all non-zero glyph widths. - let mut first = 0; - let mut width_writer = cid.widths(); - for (w, group) in widths.group_by_key(|&w| w) { - let end = first + group.len(); - if w != 0.0 { - let last = end - 1; - width_writer.same(first as u16, last as u16, w); - } - first = end; - } - - width_writer.finish(); - cid.finish(); - - let mut flags = FontFlags::empty(); - flags.set(FontFlags::SERIF, postscript_name.contains("Serif")); - flags.set(FontFlags::FIXED_PITCH, ttf.is_monospaced()); - flags.set(FontFlags::ITALIC, ttf.is_italic()); - flags.insert(FontFlags::SYMBOLIC); - flags.insert(FontFlags::SMALL_CAP); - - let global_bbox = ttf.global_bounding_box(); - let bbox = Rect::new( - font.to_em(global_bbox.x_min).to_font_units(), - font.to_em(global_bbox.y_min).to_font_units(), - font.to_em(global_bbox.x_max).to_font_units(), - font.to_em(global_bbox.y_max).to_font_units(), - ); - - let italic_angle = ttf.italic_angle().unwrap_or(0.0); - let ascender = metrics.ascender.to_font_units(); - let descender = metrics.descender.to_font_units(); - let cap_height = metrics.cap_height.to_font_units(); - let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0); - - // Write the font descriptor (contains metrics about the font). - let mut font_descriptor = ctx.writer.font_descriptor(descriptor_ref); - font_descriptor - .name(base_font) - .flags(flags) - .bbox(bbox) - .italic_angle(italic_angle) - .ascent(ascender) - .descent(descender) - .cap_height(cap_height) - .stem_v(stem_v); - - match subtype { - CidFontType::Type0 => font_descriptor.font_file3(data_ref), - CidFontType::Type2 => font_descriptor.font_file2(data_ref), - }; - - font_descriptor.finish(); - - // Write the /ToUnicode character map, which maps glyph ids back to - // unicode codepoints to enable copying out of the PDF. - let cmap = create_cmap(ttf, glyph_set); - ctx.writer.cmap(cmap_ref, &cmap.finish()); - - // Subset and write the font's bytes. - let glyphs: Vec<_> = glyph_set.keys().copied().collect(); - let data = subset_font(font, &glyphs); - let mut stream = ctx.writer.stream(data_ref, &data); - stream.filter(Filter::FlateDecode); - - if subtype == CidFontType::Type0 { - stream.pair(Name(b"Subtype"), Name(b"CIDFontType0C")); - } - - stream.finish(); - } -} - -/// Subset a font to the given glyphs. -#[comemo::memoize] -fn subset_font(font: &Font, glyphs: &[u16]) -> Bytes { - let data = font.data(); - let profile = subsetter::Profile::pdf(glyphs); - let subsetted = subsetter::subset(data, font.index(), profile); - let data = subsetted.as_deref().unwrap_or(data); - deflate(data).into() -} - -/// Create a /ToUnicode CMap. -fn create_cmap( - ttf: &ttf_parser::Face, - glyph_set: &mut BTreeMap<u16, EcoString>, -) -> UnicodeCmap { - // For glyphs that have codepoints mapping to in the font's cmap table, we - // prefer them over pre-existing text mappings from the document. Only - // things that don't have a corresponding codepoint (or only a private-use - // one) like the "Th" in Linux Libertine get the text of their first - // occurrences in the document instead. - for subtable in ttf.tables().cmap.into_iter().flat_map(|table| table.subtables) { - if !subtable.is_unicode() { - continue; - } - - subtable.codepoints(|n| { - let Some(c) = std::char::from_u32(n) else { return }; - if unicode_general_category::get_general_category(c) - == GeneralCategory::PrivateUse - { - return; - } - - let Some(GlyphId(g)) = ttf.glyph_index(c) else { return }; - if glyph_set.contains_key(&g) { - glyph_set.insert(g, c.into()); - } - }); - } - - // Produce a reverse mapping from glyphs to unicode strings. - let mut cmap = UnicodeCmap::new(CMAP_NAME, SYSTEM_INFO); - for (&g, text) in glyph_set.iter() { - if !text.is_empty() { - cmap.pair_with_multiple(g, text.chars()); - } - } - - cmap -} diff --git a/src/export/pdf/image.rs b/src/export/pdf/image.rs deleted file mode 100644 index 48472d9f..00000000 --- a/src/export/pdf/image.rs +++ /dev/null @@ -1,143 +0,0 @@ -use std::io::Cursor; - -use image::{DynamicImage, GenericImageView, Rgba}; -use pdf_writer::{Filter, Finish}; - -use super::{deflate, PdfContext, RefExt}; -use crate::image::{DecodedImage, Image, RasterFormat}; -use crate::util::Bytes; - -/// Embed all used images into the PDF. -#[tracing::instrument(skip_all)] -pub fn write_images(ctx: &mut PdfContext) { - for image in ctx.image_map.items() { - let image_ref = ctx.alloc.bump(); - let icc_ref = ctx.alloc.bump(); - ctx.image_refs.push(image_ref); - - let width = image.width(); - let height = image.height(); - - // Add the primary image. - // TODO: Error if image could not be encoded. - match image.decoded().as_ref() { - DecodedImage::Raster(dynamic, icc, _) => { - // TODO: Error if image could not be encoded. - let (data, filter, has_color) = encode_image(image); - let mut image = ctx.writer.image_xobject(image_ref, &data); - image.filter(filter); - image.width(width as i32); - image.height(height as i32); - image.bits_per_component(8); - - let space = image.color_space(); - if icc.is_some() { - space.icc_based(icc_ref); - } else if has_color { - space.device_rgb(); - } else { - space.device_gray(); - } - - // Add a second gray-scale image containing the alpha values if - // this image has an alpha channel. - if dynamic.color().has_alpha() { - let (alpha_data, alpha_filter) = encode_alpha(dynamic); - let mask_ref = ctx.alloc.bump(); - image.s_mask(mask_ref); - image.finish(); - - let mut mask = ctx.writer.image_xobject(mask_ref, &alpha_data); - mask.filter(alpha_filter); - mask.width(width as i32); - mask.height(height as i32); - mask.color_space().device_gray(); - mask.bits_per_component(8); - } else { - image.finish(); - } - - if let Some(icc) = icc { - let compressed = deflate(&icc.0); - let mut stream = ctx.writer.icc_profile(icc_ref, &compressed); - stream.filter(Filter::FlateDecode); - if has_color { - stream.n(3); - stream.alternate().srgb(); - } else { - stream.n(1); - stream.alternate().d65_gray(); - } - } - } - DecodedImage::Svg(svg) => { - let next_ref = svg2pdf::convert_tree_into( - svg, - svg2pdf::Options::default(), - &mut ctx.writer, - image_ref, - ); - ctx.alloc = next_ref; - } - } - } -} - -/// Encode an image with a suitable filter and return the data, filter and -/// whether the image has color. -/// -/// Skips the alpha channel as that's encoded separately. -#[comemo::memoize] -#[tracing::instrument(skip_all)] -fn encode_image(image: &Image) -> (Bytes, Filter, bool) { - let decoded = image.decoded(); - let (dynamic, format) = match decoded.as_ref() { - DecodedImage::Raster(dynamic, _, format) => (dynamic, *format), - _ => panic!("can only encode raster image"), - }; - - match (format, dynamic) { - // 8-bit gray JPEG. - (RasterFormat::Jpg, DynamicImage::ImageLuma8(_)) => { - let mut data = Cursor::new(vec![]); - dynamic.write_to(&mut data, image::ImageFormat::Jpeg).unwrap(); - (data.into_inner().into(), Filter::DctDecode, false) - } - - // 8-bit RGB JPEG (CMYK JPEGs get converted to RGB earlier). - (RasterFormat::Jpg, DynamicImage::ImageRgb8(_)) => { - let mut data = Cursor::new(vec![]); - dynamic.write_to(&mut data, image::ImageFormat::Jpeg).unwrap(); - (data.into_inner().into(), Filter::DctDecode, true) - } - - // TODO: Encode flate streams with PNG-predictor? - - // 8-bit gray PNG. - (RasterFormat::Png, DynamicImage::ImageLuma8(luma)) => { - let data = deflate(luma.as_raw()); - (data.into(), Filter::FlateDecode, false) - } - - // Anything else (including Rgb(a) PNGs). - (_, buf) => { - let (width, height) = buf.dimensions(); - let mut pixels = Vec::with_capacity(3 * width as usize * height as usize); - for (_, _, Rgba([r, g, b, _])) in buf.pixels() { - pixels.push(r); - pixels.push(g); - pixels.push(b); - } - - let data = deflate(&pixels); - (data.into(), Filter::FlateDecode, true) - } - } -} - -/// Encode an image's alpha channel if present. -#[tracing::instrument(skip_all)] -fn encode_alpha(dynamic: &DynamicImage) -> (Vec<u8>, Filter) { - let pixels: Vec<_> = dynamic.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect(); - (deflate(&pixels), Filter::FlateDecode) -} diff --git a/src/export/pdf/mod.rs b/src/export/pdf/mod.rs deleted file mode 100644 index 48485862..00000000 --- a/src/export/pdf/mod.rs +++ /dev/null @@ -1,235 +0,0 @@ -//! Exporting into PDF documents. - -mod font; -mod image; -mod outline; -mod page; - -use std::cmp::Eq; -use std::collections::{BTreeMap, HashMap}; -use std::hash::Hash; - -use ecow::EcoString; -use pdf_writer::types::Direction; -use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr}; -use xmp_writer::{LangId, RenditionClass, XmpWriter}; - -use self::page::Page; -use crate::doc::{Document, Lang}; -use crate::font::Font; -use crate::geom::{Abs, Dir, Em}; -use crate::image::Image; -use crate::model::Introspector; - -/// Export a document into a PDF file. -/// -/// Returns the raw bytes making up the PDF file. -#[tracing::instrument(skip_all)] -pub fn pdf(document: &Document) -> Vec<u8> { - let mut ctx = PdfContext::new(document); - page::construct_pages(&mut ctx, &document.pages); - font::write_fonts(&mut ctx); - image::write_images(&mut ctx); - page::write_page_tree(&mut ctx); - write_catalog(&mut ctx); - ctx.writer.finish() -} - -/// Identifies the color space definitions. -const SRGB: Name<'static> = Name(b"srgb"); -const D65_GRAY: Name<'static> = Name(b"d65gray"); - -/// Context for exporting a whole PDF document. -pub struct PdfContext<'a> { - document: &'a Document, - introspector: Introspector, - writer: PdfWriter, - pages: Vec<Page>, - page_heights: Vec<f32>, - alloc: Ref, - page_tree_ref: Ref, - font_refs: Vec<Ref>, - image_refs: Vec<Ref>, - page_refs: Vec<Ref>, - font_map: Remapper<Font>, - image_map: Remapper<Image>, - /// For each font a mapping from used glyphs to their text representation. - /// May contain multiple chars in case of ligatures or similar things. The - /// same glyph can have a different text representation within one document, - /// then we just save the first one. The resulting strings are used for the - /// PDF's /ToUnicode map for glyphs that don't have an entry in the font's - /// cmap. This is important for copy-paste and searching. - glyph_sets: HashMap<Font, BTreeMap<u16, EcoString>>, - languages: HashMap<Lang, usize>, -} - -impl<'a> PdfContext<'a> { - fn new(document: &'a Document) -> Self { - let mut alloc = Ref::new(1); - let page_tree_ref = alloc.bump(); - Self { - document, - introspector: Introspector::new(&document.pages), - writer: PdfWriter::new(), - pages: vec![], - page_heights: vec![], - alloc, - page_tree_ref, - page_refs: vec![], - font_refs: vec![], - image_refs: vec![], - font_map: Remapper::new(), - image_map: Remapper::new(), - glyph_sets: HashMap::new(), - languages: HashMap::new(), - } - } -} - -/// Write the document catalog. -#[tracing::instrument(skip_all)] -fn write_catalog(ctx: &mut PdfContext) { - let lang = ctx - .languages - .iter() - .max_by_key(|(&lang, &count)| (count, lang)) - .map(|(&k, _)| k); - - let dir = if lang.map(Lang::dir) == Some(Dir::RTL) { - Direction::R2L - } else { - Direction::L2R - }; - - // Write the outline tree. - let outline_root_id = outline::write_outline(ctx); - - // Write the document information. - let mut info = ctx.writer.document_info(ctx.alloc.bump()); - let mut xmp = XmpWriter::new(); - if let Some(title) = &ctx.document.title { - info.title(TextStr(title)); - xmp.title([(None, title.as_str())]); - } - - let authors = &ctx.document.author; - if !authors.is_empty() { - info.author(TextStr(&authors.join(", "))); - xmp.creator(authors.iter().map(|s| s.as_str())); - } - info.creator(TextStr("Typst")); - info.finish(); - xmp.creator_tool("Typst"); - xmp.num_pages(ctx.document.pages.len() as u32); - xmp.format("application/pdf"); - xmp.language(ctx.languages.keys().map(|lang| LangId(lang.as_str()))); - xmp.rendition_class(RenditionClass::Proof); - xmp.pdf_version("1.7"); - - let xmp_buf = xmp.finish(None); - let meta_ref = ctx.alloc.bump(); - let mut meta_stream = ctx.writer.stream(meta_ref, xmp_buf.as_bytes()); - meta_stream.pair(Name(b"Type"), Name(b"Metadata")); - meta_stream.pair(Name(b"Subtype"), Name(b"XML")); - meta_stream.finish(); - - // Write the document catalog. - let mut catalog = ctx.writer.catalog(ctx.alloc.bump()); - catalog.pages(ctx.page_tree_ref); - catalog.viewer_preferences().direction(dir); - catalog.pair(Name(b"Metadata"), meta_ref); - - if let Some(outline_root_id) = outline_root_id { - catalog.outlines(outline_root_id); - } - - if let Some(lang) = lang { - catalog.lang(TextStr(lang.as_str())); - } -} - -/// Compress data with the DEFLATE algorithm. -#[tracing::instrument(skip_all)] -fn deflate(data: &[u8]) -> Vec<u8> { - const COMPRESSION_LEVEL: u8 = 6; - miniz_oxide::deflate::compress_to_vec_zlib(data, COMPRESSION_LEVEL) -} - -/// Assigns new, consecutive PDF-internal indices to items. -struct Remapper<T> { - /// Forwards from the items to the pdf indices. - to_pdf: HashMap<T, usize>, - /// Backwards from the pdf indices to the items. - to_items: Vec<T>, -} - -impl<T> Remapper<T> -where - T: Eq + Hash + Clone, -{ - fn new() -> Self { - Self { to_pdf: HashMap::new(), to_items: vec![] } - } - - fn insert(&mut self, item: T) { - let to_layout = &mut self.to_items; - self.to_pdf.entry(item.clone()).or_insert_with(|| { - let pdf_index = to_layout.len(); - to_layout.push(item); - pdf_index - }); - } - - fn map(&self, item: T) -> usize { - self.to_pdf[&item] - } - - fn pdf_indices<'a>( - &'a self, - refs: &'a [Ref], - ) -> impl Iterator<Item = (Ref, usize)> + 'a { - refs.iter().copied().zip(0..self.to_pdf.len()) - } - - fn items(&self) -> impl Iterator<Item = &T> + '_ { - self.to_items.iter() - } -} - -/// Additional methods for [`Abs`]. -trait AbsExt { - /// Convert an to a number of points. - fn to_f32(self) -> f32; -} - -impl AbsExt for Abs { - fn to_f32(self) -> f32 { - self.to_pt() as f32 - } -} - -/// Additional methods for [`Em`]. -trait EmExt { - /// Convert an em length to a number of PDF font units. - fn to_font_units(self) -> f32; -} - -impl EmExt for Em { - fn to_font_units(self) -> f32 { - 1000.0 * self.get() as f32 - } -} - -/// Additional methods for [`Ref`]. -trait RefExt { - /// Bump the reference up by one and return the previous one. - fn bump(&mut self) -> Self; -} - -impl RefExt for Ref { - fn bump(&mut self) -> Self { - let prev = *self; - *self = Self::new(prev.get() + 1); - prev - } -} diff --git a/src/export/pdf/outline.rs b/src/export/pdf/outline.rs deleted file mode 100644 index 539647eb..00000000 --- a/src/export/pdf/outline.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::num::NonZeroUsize; - -use pdf_writer::{Finish, Ref, TextStr}; - -use super::{AbsExt, PdfContext, RefExt}; -use crate::geom::Abs; -use crate::model::Content; - -/// Construct the outline for the document. -#[tracing::instrument(skip_all)] -pub fn write_outline(ctx: &mut PdfContext) -> Option<Ref> { - let mut tree: Vec<HeadingNode> = vec![]; - for heading in ctx.introspector.query(&item!(heading_func).select()) { - let leaf = HeadingNode::leaf((*heading).clone()); - - let mut children = &mut tree; - while children.last().map_or(false, |last| last.level < leaf.level) { - children = &mut children.last_mut().unwrap().children; - } - - children.push(leaf); - } - - if tree.is_empty() { - return None; - } - - let root_id = ctx.alloc.bump(); - let start_ref = ctx.alloc; - let len = tree.len(); - - let mut prev_ref = None; - for (i, node) in tree.iter().enumerate() { - prev_ref = Some(write_outline_item(ctx, node, root_id, prev_ref, i + 1 == len)); - } - - ctx.writer - .outline(root_id) - .first(start_ref) - .last(Ref::new(ctx.alloc.get() - 1)) - .count(tree.len() as i32); - - Some(root_id) -} - -/// A heading in the outline panel. -#[derive(Debug, Clone)] -struct HeadingNode { - element: Content, - level: NonZeroUsize, - children: Vec<HeadingNode>, -} - -impl HeadingNode { - fn leaf(element: Content) -> Self { - HeadingNode { - level: element.expect_field::<NonZeroUsize>("level"), - element, - children: Vec::new(), - } - } - - fn len(&self) -> usize { - 1 + self.children.iter().map(Self::len).sum::<usize>() - } -} - -/// Write an outline item and all its children. -#[tracing::instrument(skip_all)] -fn write_outline_item( - ctx: &mut PdfContext, - node: &HeadingNode, - parent_ref: Ref, - prev_ref: Option<Ref>, - is_last: bool, -) -> Ref { - let id = ctx.alloc.bump(); - let next_ref = Ref::new(id.get() + node.len() as i32); - - let mut outline = ctx.writer.outline_item(id); - outline.parent(parent_ref); - - if !is_last { - outline.next(next_ref); - } - - if let Some(prev_rev) = prev_ref { - outline.prev(prev_rev); - } - - if !node.children.is_empty() { - let current_child = Ref::new(id.get() + 1); - outline.first(current_child); - outline.last(Ref::new(next_ref.get() - 1)); - outline.count(-(node.children.len() as i32)); - } - - let body = node.element.expect_field::<Content>("body"); - outline.title(TextStr(body.plain_text().trim())); - - let loc = node.element.location().unwrap(); - let pos = ctx.introspector.position(loc); - let index = pos.page.get() - 1; - if let Some(&height) = ctx.page_heights.get(index) { - let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); - outline.dest().page(ctx.page_refs[index]).xyz( - pos.point.x.to_f32(), - height - y.to_f32(), - None, - ); - } - - outline.finish(); - - let mut prev_ref = None; - for (i, child) in node.children.iter().enumerate() { - prev_ref = Some(write_outline_item( - ctx, - child, - id, - prev_ref, - i + 1 == node.children.len(), - )); - } - - id -} diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs deleted file mode 100644 index 22e590d5..00000000 --- a/src/export/pdf/page.rs +++ /dev/null @@ -1,565 +0,0 @@ -use ecow::eco_format; -use pdf_writer::types::{ - ActionType, AnnotationType, ColorSpaceOperand, LineCapStyle, LineJoinStyle, -}; -use pdf_writer::writers::ColorSpace; -use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str}; - -use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB}; -use crate::doc::{Destination, Frame, FrameItem, GroupItem, Meta, TextItem}; -use crate::font::Font; -use crate::geom::{ - self, Abs, Color, Em, Geometry, LineCap, LineJoin, Numeric, Paint, Point, Ratio, - Shape, Size, Stroke, Transform, -}; -use crate::image::Image; - -/// Construct page objects. -#[tracing::instrument(skip_all)] -pub fn construct_pages(ctx: &mut PdfContext, frames: &[Frame]) { - for frame in frames { - construct_page(ctx, frame); - } -} - -/// Construct a page object. -#[tracing::instrument(skip_all)] -pub fn construct_page(ctx: &mut PdfContext, frame: &Frame) { - let page_ref = ctx.alloc.bump(); - ctx.page_refs.push(page_ref); - ctx.page_heights.push(frame.height().to_f32()); - - let mut ctx = PageContext { - parent: ctx, - page_ref, - content: Content::new(), - state: State::default(), - saves: vec![], - bottom: 0.0, - links: vec![], - }; - - let size = frame.size(); - - // Make the coordinate system start at the top-left. - ctx.bottom = size.y.to_f32(); - ctx.transform(Transform { - sx: Ratio::one(), - ky: Ratio::zero(), - kx: Ratio::zero(), - sy: Ratio::new(-1.0), - tx: Abs::zero(), - ty: size.y, - }); - - // Encode the page into the content stream. - write_frame(&mut ctx, frame); - - let page = Page { - size, - content: ctx.content, - id: ctx.page_ref, - links: ctx.links, - }; - - ctx.parent.pages.push(page); -} - -/// Write the page tree. -#[tracing::instrument(skip_all)] -pub fn write_page_tree(ctx: &mut PdfContext) { - for page in std::mem::take(&mut ctx.pages).into_iter() { - write_page(ctx, page); - } - - let mut pages = ctx.writer.pages(ctx.page_tree_ref); - pages - .count(ctx.page_refs.len() as i32) - .kids(ctx.page_refs.iter().copied()); - - let mut resources = pages.resources(); - let mut spaces = resources.color_spaces(); - spaces.insert(SRGB).start::<ColorSpace>().srgb(); - spaces.insert(D65_GRAY).start::<ColorSpace>().d65_gray(); - spaces.finish(); - - let mut fonts = resources.fonts(); - for (font_ref, f) in ctx.font_map.pdf_indices(&ctx.font_refs) { - let name = eco_format!("F{}", f); - fonts.pair(Name(name.as_bytes()), font_ref); - } - - fonts.finish(); - - let mut images = resources.x_objects(); - for (image_ref, im) in ctx.image_map.pdf_indices(&ctx.image_refs) { - let name = eco_format!("Im{}", im); - images.pair(Name(name.as_bytes()), image_ref); - } - - images.finish(); - resources.finish(); - pages.finish(); -} - -/// Write a page tree node. -#[tracing::instrument(skip_all)] -fn write_page(ctx: &mut PdfContext, page: Page) { - let content_id = ctx.alloc.bump(); - - let mut page_writer = ctx.writer.page(page.id); - page_writer.parent(ctx.page_tree_ref); - - let w = page.size.x.to_f32(); - let h = page.size.y.to_f32(); - page_writer.media_box(Rect::new(0.0, 0.0, w, h)); - page_writer.contents(content_id); - - let mut annotations = page_writer.annotations(); - for (dest, rect) in page.links { - let mut annotation = annotations.push(); - annotation.subtype(AnnotationType::Link).rect(rect); - annotation.border(0.0, 0.0, 0.0, None); - - let pos = match dest { - Destination::Url(uri) => { - annotation - .action() - .action_type(ActionType::Uri) - .uri(Str(uri.as_bytes())); - continue; - } - Destination::Position(pos) => pos, - Destination::Location(loc) => ctx.introspector.position(loc), - }; - - let index = pos.page.get() - 1; - let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); - if let Some(&height) = ctx.page_heights.get(index) { - annotation - .action() - .action_type(ActionType::GoTo) - .destination() - .page(ctx.page_refs[index]) - .xyz(pos.point.x.to_f32(), height - y.to_f32(), None); - } - } - - annotations.finish(); - page_writer.finish(); - - let data = page.content.finish(); - let data = deflate(&data); - ctx.writer.stream(content_id, &data).filter(Filter::FlateDecode); -} - -/// Data for an exported page. -pub struct Page { - /// The indirect object id of the page. - pub id: Ref, - /// The page's dimensions. - pub size: Size, - /// The page's content stream. - pub content: Content, - /// Links in the PDF coordinate system. - pub links: Vec<(Destination, Rect)>, -} - -/// An exporter for the contents of a single PDF page. -struct PageContext<'a, 'b> { - parent: &'a mut PdfContext<'b>, - page_ref: Ref, - content: Content, - state: State, - saves: Vec<State>, - bottom: f32, - links: Vec<(Destination, Rect)>, -} - -/// A simulated graphics state used to deduplicate graphics state changes and -/// keep track of the current transformation matrix for link annotations. -#[derive(Debug, Default, Clone)] -struct State { - transform: Transform, - font: Option<(Font, Abs)>, - fill: Option<Paint>, - fill_space: Option<Name<'static>>, - stroke: Option<Stroke>, - stroke_space: Option<Name<'static>>, -} - -impl PageContext<'_, '_> { - fn save_state(&mut self) { - self.saves.push(self.state.clone()); - self.content.save_state(); - } - - fn restore_state(&mut self) { - self.content.restore_state(); - self.state = self.saves.pop().expect("missing state save"); - } - - fn transform(&mut self, transform: Transform) { - let Transform { sx, ky, kx, sy, tx, ty } = transform; - self.state.transform = self.state.transform.pre_concat(transform); - self.content.transform([ - sx.get() as _, - ky.get() as _, - kx.get() as _, - sy.get() as _, - tx.to_f32(), - ty.to_f32(), - ]); - } - - fn set_font(&mut self, font: &Font, size: Abs) { - if self.state.font.as_ref().map(|(f, s)| (f, *s)) != Some((font, size)) { - self.parent.font_map.insert(font.clone()); - let name = eco_format!("F{}", self.parent.font_map.map(font.clone())); - self.content.set_font(Name(name.as_bytes()), size.to_f32()); - self.state.font = Some((font.clone(), size)); - } - } - - fn set_fill(&mut self, fill: &Paint) { - if self.state.fill.as_ref() != Some(fill) { - let f = |c| c as f32 / 255.0; - let Paint::Solid(color) = fill; - match color { - Color::Luma(c) => { - self.set_fill_color_space(D65_GRAY); - self.content.set_fill_gray(f(c.0)); - } - Color::Rgba(c) => { - self.set_fill_color_space(SRGB); - self.content.set_fill_color([f(c.r), f(c.g), f(c.b)]); - } - Color::Cmyk(c) => { - self.reset_fill_color_space(); - self.content.set_fill_cmyk(f(c.c), f(c.m), f(c.y), f(c.k)); - } - } - self.state.fill = Some(fill.clone()); - } - } - - fn set_fill_color_space(&mut self, space: Name<'static>) { - if self.state.fill_space != Some(space) { - self.content.set_fill_color_space(ColorSpaceOperand::Named(space)); - self.state.fill_space = Some(space); - } - } - - fn reset_fill_color_space(&mut self) { - self.state.fill_space = None; - } - - fn set_stroke(&mut self, stroke: &Stroke) { - if self.state.stroke.as_ref() != Some(stroke) { - let Stroke { - paint, - thickness, - line_cap, - line_join, - dash_pattern, - miter_limit, - } = stroke; - - let f = |c| c as f32 / 255.0; - let Paint::Solid(color) = paint; - match color { - Color::Luma(c) => { - self.set_stroke_color_space(D65_GRAY); - self.content.set_stroke_gray(f(c.0)); - } - Color::Rgba(c) => { - self.set_stroke_color_space(SRGB); - self.content.set_stroke_color([f(c.r), f(c.g), f(c.b)]); - } - Color::Cmyk(c) => { - self.reset_stroke_color_space(); - self.content.set_stroke_cmyk(f(c.c), f(c.m), f(c.y), f(c.k)); - } - } - - self.content.set_line_width(thickness.to_f32()); - if self.state.stroke.as_ref().map(|s| &s.line_cap) != Some(line_cap) { - self.content.set_line_cap(line_cap.into()); - } - if self.state.stroke.as_ref().map(|s| &s.line_join) != Some(line_join) { - self.content.set_line_join(line_join.into()); - } - if self.state.stroke.as_ref().map(|s| &s.dash_pattern) != Some(dash_pattern) { - if let Some(pattern) = dash_pattern { - self.content.set_dash_pattern( - pattern.array.iter().map(|l| l.to_f32()), - pattern.phase.to_f32(), - ); - } else { - self.content.set_dash_pattern([], 0.0); - } - } - if self.state.stroke.as_ref().map(|s| &s.miter_limit) != Some(miter_limit) { - self.content.set_miter_limit(miter_limit.0 as f32); - } - self.state.stroke = Some(stroke.clone()); - } - } - - fn set_stroke_color_space(&mut self, space: Name<'static>) { - if self.state.stroke_space != Some(space) { - self.content.set_stroke_color_space(ColorSpaceOperand::Named(space)); - self.state.stroke_space = Some(space); - } - } - - fn reset_stroke_color_space(&mut self) { - self.state.stroke_space = None; - } -} - -/// Encode a frame into the content stream. -fn write_frame(ctx: &mut PageContext, frame: &Frame) { - for &(pos, ref item) in frame.items() { - let x = pos.x.to_f32(); - let y = pos.y.to_f32(); - match item { - FrameItem::Group(group) => write_group(ctx, pos, group), - FrameItem::Text(text) => write_text(ctx, x, y, text), - FrameItem::Shape(shape, _) => write_shape(ctx, x, y, shape), - FrameItem::Image(image, size, _) => write_image(ctx, x, y, image, *size), - FrameItem::Meta(meta, size) => match meta { - Meta::Link(dest) => write_link(ctx, pos, dest, *size), - Meta::Elem(_) => {} - Meta::Hide => {} - Meta::PageNumbering(_) => {} - }, - } - } -} - -/// Encode a group into the content stream. -fn write_group(ctx: &mut PageContext, pos: Point, group: &GroupItem) { - let translation = Transform::translate(pos.x, pos.y); - - ctx.save_state(); - ctx.transform(translation.pre_concat(group.transform)); - - if group.clips { - let size = group.frame.size(); - let w = size.x.to_f32(); - let h = size.y.to_f32(); - ctx.content.move_to(0.0, 0.0); - ctx.content.line_to(w, 0.0); - ctx.content.line_to(w, h); - ctx.content.line_to(0.0, h); - ctx.content.clip_nonzero(); - ctx.content.end_path(); - } - - write_frame(ctx, &group.frame); - ctx.restore_state(); -} - -/// Encode a text run into the content stream. -fn write_text(ctx: &mut PageContext, x: f32, y: f32, text: &TextItem) { - *ctx.parent.languages.entry(text.lang).or_insert(0) += text.glyphs.len(); - - let glyph_set = ctx.parent.glyph_sets.entry(text.font.clone()).or_default(); - for g in &text.glyphs { - let segment = &text.text[g.range()]; - glyph_set.entry(g.id).or_insert_with(|| segment.into()); - } - - ctx.set_fill(&text.fill); - ctx.set_font(&text.font, text.size); - ctx.content.begin_text(); - - // Positiosn the text. - ctx.content.set_text_matrix([1.0, 0.0, 0.0, -1.0, x, y]); - - let mut positioned = ctx.content.show_positioned(); - let mut items = positioned.items(); - let mut adjustment = Em::zero(); - let mut encoded = vec![]; - - // Write the glyphs with kerning adjustments. - for glyph in &text.glyphs { - adjustment += glyph.x_offset; - - if !adjustment.is_zero() { - if !encoded.is_empty() { - items.show(Str(&encoded)); - encoded.clear(); - } - - items.adjust(-adjustment.to_font_units()); - adjustment = Em::zero(); - } - - encoded.push((glyph.id >> 8) as u8); - encoded.push((glyph.id & 0xff) as u8); - - if let Some(advance) = text.font.advance(glyph.id) { - adjustment += glyph.x_advance - advance; - } - - adjustment -= glyph.x_offset; - } - - if !encoded.is_empty() { - items.show(Str(&encoded)); - } - - items.finish(); - positioned.finish(); - ctx.content.end_text(); -} - -/// Encode a geometrical shape into the content stream. -fn write_shape(ctx: &mut PageContext, x: f32, y: f32, shape: &Shape) { - let stroke = shape.stroke.as_ref().and_then(|stroke| { - if stroke.thickness.to_f32() > 0.0 { - Some(stroke) - } else { - None - } - }); - - if shape.fill.is_none() && stroke.is_none() { - return; - } - - if let Some(fill) = &shape.fill { - ctx.set_fill(fill); - } - - if let Some(stroke) = stroke { - ctx.set_stroke(stroke); - } - - match shape.geometry { - Geometry::Line(target) => { - let dx = target.x.to_f32(); - let dy = target.y.to_f32(); - ctx.content.move_to(x, y); - ctx.content.line_to(x + dx, y + dy); - } - Geometry::Rect(size) => { - let w = size.x.to_f32(); - let h = size.y.to_f32(); - if w > 0.0 && h > 0.0 { - ctx.content.rect(x, y, w, h); - } - } - Geometry::Path(ref path) => { - write_path(ctx, x, y, path); - } - } - - match (&shape.fill, stroke) { - (None, None) => unreachable!(), - (Some(_), None) => ctx.content.fill_nonzero(), - (None, Some(_)) => ctx.content.stroke(), - (Some(_), Some(_)) => ctx.content.fill_nonzero_and_stroke(), - }; -} - -/// Encode a bezier path into the content stream. -fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &geom::Path) { - for elem in &path.0 { - match elem { - geom::PathItem::MoveTo(p) => { - ctx.content.move_to(x + p.x.to_f32(), y + p.y.to_f32()) - } - geom::PathItem::LineTo(p) => { - ctx.content.line_to(x + p.x.to_f32(), y + p.y.to_f32()) - } - geom::PathItem::CubicTo(p1, p2, p3) => ctx.content.cubic_to( - x + p1.x.to_f32(), - y + p1.y.to_f32(), - x + p2.x.to_f32(), - y + p2.y.to_f32(), - x + p3.x.to_f32(), - y + p3.y.to_f32(), - ), - geom::PathItem::ClosePath => ctx.content.close_path(), - }; - } -} - -/// Encode a vector or raster image into the content stream. -fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size) { - ctx.parent.image_map.insert(image.clone()); - let name = eco_format!("Im{}", ctx.parent.image_map.map(image.clone())); - let w = size.x.to_f32(); - let h = size.y.to_f32(); - ctx.content.save_state(); - ctx.content.transform([w, 0.0, 0.0, -h, x, y + h]); - - if let Some(alt) = image.alt() { - let mut image_span = - ctx.content.begin_marked_content_with_properties(Name(b"Span")); - let mut image_alt = image_span.properties(); - image_alt.pair(Name(b"Alt"), pdf_writer::Str(alt.as_bytes())); - image_alt.finish(); - image_span.finish(); - - ctx.content.x_object(Name(name.as_bytes())); - ctx.content.end_marked_content(); - } else { - ctx.content.x_object(Name(name.as_bytes())); - } - - ctx.content.restore_state(); -} - -/// Save a link for later writing in the annotations dictionary. -fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size) { - let mut min_x = Abs::inf(); - let mut min_y = Abs::inf(); - let mut max_x = -Abs::inf(); - let mut max_y = -Abs::inf(); - - // Compute the bounding box of the transformed link. - for point in [ - pos, - pos + Point::with_x(size.x), - pos + Point::with_y(size.y), - pos + size.to_point(), - ] { - let t = point.transform(ctx.state.transform); - min_x.set_min(t.x); - min_y.set_min(t.y); - max_x.set_max(t.x); - max_y.set_max(t.y); - } - - let x1 = min_x.to_f32(); - let x2 = max_x.to_f32(); - let y1 = max_y.to_f32(); - let y2 = min_y.to_f32(); - let rect = Rect::new(x1, y1, x2, y2); - - ctx.links.push((dest.clone(), rect)); -} - -impl From<&LineCap> for LineCapStyle { - fn from(line_cap: &LineCap) -> Self { - match line_cap { - LineCap::Butt => LineCapStyle::ButtCap, - LineCap::Round => LineCapStyle::RoundCap, - LineCap::Square => LineCapStyle::ProjectingSquareCap, - } - } -} - -impl From<&LineJoin> for LineJoinStyle { - fn from(line_join: &LineJoin) -> Self { - match line_join { - LineJoin::Miter => LineJoinStyle::MiterJoin, - LineJoin::Round => LineJoinStyle::RoundJoin, - LineJoin::Bevel => LineJoinStyle::BevelJoin, - } - } -} diff --git a/src/export/render.rs b/src/export/render.rs deleted file mode 100644 index d8115b12..00000000 --- a/src/export/render.rs +++ /dev/null @@ -1,673 +0,0 @@ -//! Rendering into raster images. - -use std::io::Read; -use std::sync::Arc; - -use image::imageops::FilterType; -use image::{GenericImageView, Rgba}; -use pixglyph::Bitmap; -use resvg::FitTo; -use tiny_skia as sk; -use ttf_parser::{GlyphId, OutlineBuilder}; -use usvg::{NodeExt, TreeParsing}; - -use crate::doc::{Frame, FrameItem, GroupItem, Meta, TextItem}; -use crate::font::Font; -use crate::geom::{ - self, Abs, Color, Geometry, LineCap, LineJoin, Paint, PathItem, Shape, Size, Stroke, - Transform, -}; -use crate::image::{DecodedImage, Image}; - -/// Export a frame into a raster image. -/// -/// This renders the frame at the given number of pixels per point and returns -/// the resulting `tiny-skia` pixel buffer. -pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap { - let size = frame.size(); - let pxw = (pixel_per_pt * size.x.to_f32()).round().max(1.0) as u32; - let pxh = (pixel_per_pt * size.y.to_f32()).round().max(1.0) as u32; - - let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap(); - canvas.fill(fill.into()); - - let ts = sk::Transform::from_scale(pixel_per_pt, pixel_per_pt); - render_frame(&mut canvas, ts, None, frame); - - canvas -} - -/// Render a frame into the canvas. -fn render_frame( - canvas: &mut sk::Pixmap, - ts: sk::Transform, - mask: Option<&sk::Mask>, - frame: &Frame, -) { - for (pos, item) in frame.items() { - let x = pos.x.to_f32(); - let y = pos.y.to_f32(); - let ts = ts.pre_translate(x, y); - - match item { - FrameItem::Group(group) => { - render_group(canvas, ts, mask, group); - } - FrameItem::Text(text) => { - render_text(canvas, ts, mask, text); - } - FrameItem::Shape(shape, _) => { - render_shape(canvas, ts, mask, shape); - } - FrameItem::Image(image, size, _) => { - render_image(canvas, ts, mask, image, *size); - } - FrameItem::Meta(meta, _) => match meta { - Meta::Link(_) => {} - Meta::Elem(_) => {} - Meta::PageNumbering(_) => {} - Meta::Hide => {} - }, - } - } -} - -/// Render a group frame with optional transform and clipping into the canvas. -fn render_group( - canvas: &mut sk::Pixmap, - ts: sk::Transform, - mask: Option<&sk::Mask>, - group: &GroupItem, -) { - let ts = ts.pre_concat(group.transform.into()); - - let mut mask = mask; - let storage; - if group.clips { - let size = group.frame.size(); - let w = size.x.to_f32(); - let h = size.y.to_f32(); - if let Some(path) = sk::Rect::from_xywh(0.0, 0.0, w, h) - .map(sk::PathBuilder::from_rect) - .and_then(|path| path.transform(ts)) - { - if let Some(mask) = mask { - let mut mask = mask.clone(); - mask.intersect_path( - &path, - sk::FillRule::default(), - false, - sk::Transform::default(), - ); - storage = mask; - } else { - let pxw = canvas.width(); - let pxh = canvas.height(); - let Some(mut mask) = sk::Mask::new(pxw, pxh) else { - // Fails if clipping rect is empty. In that case we just - // clip everything by returning. - return; - }; - - mask.fill_path( - &path, - sk::FillRule::default(), - false, - sk::Transform::default(), - ); - storage = mask; - }; - - mask = Some(&storage); - } - } - - render_frame(canvas, ts, mask, &group.frame); -} - -/// Render a text run into the canvas. -fn render_text( - canvas: &mut sk::Pixmap, - ts: sk::Transform, - mask: Option<&sk::Mask>, - text: &TextItem, -) { - let mut x = 0.0; - for glyph in &text.glyphs { - let id = GlyphId(glyph.id); - let offset = x + glyph.x_offset.at(text.size).to_f32(); - let ts = ts.pre_translate(offset, 0.0); - - render_svg_glyph(canvas, ts, mask, text, id) - .or_else(|| render_bitmap_glyph(canvas, ts, mask, text, id)) - .or_else(|| render_outline_glyph(canvas, ts, mask, text, id)); - - x += glyph.x_advance.at(text.size).to_f32(); - } -} - -/// Render an SVG glyph into the canvas. -fn render_svg_glyph( - canvas: &mut sk::Pixmap, - ts: sk::Transform, - mask: Option<&sk::Mask>, - text: &TextItem, - id: GlyphId, -) -> Option<()> { - let mut data = text.font.ttf().glyph_svg_image(id)?; - - // Decompress SVGZ. - let mut decoded = vec![]; - if data.starts_with(&[0x1f, 0x8b]) { - let mut decoder = flate2::read::GzDecoder::new(data); - decoder.read_to_end(&mut decoded).ok()?; - data = &decoded; - } - - // Parse XML. - let xml = std::str::from_utf8(data).ok()?; - let document = roxmltree::Document::parse(xml).ok()?; - let root = document.root_element(); - - // Parse SVG. - let opts = usvg::Options::default(); - let tree = usvg::Tree::from_xmltree(&document, &opts).ok()?; - let view_box = tree.view_box.rect; - - // If there's no viewbox defined, use the em square for our scale - // transformation ... - let upem = text.font.units_per_em() as f32; - let (mut width, mut height) = (upem, upem); - - // ... but if there's a viewbox or width, use that. - if root.has_attribute("viewBox") || root.has_attribute("width") { - width = view_box.width() as f32; - } - - // Same as for width. - if root.has_attribute("viewBox") || root.has_attribute("height") { - height = view_box.height() as f32; - } - - let size = text.size.to_f32(); - let ts = ts.pre_scale(size / width, size / height); - - // Compute the space we need to draw our glyph. - // See https://github.com/RazrFalcon/resvg/issues/602 for why - // using the svg size is problematic here. - let mut bbox = usvg::Rect::new_bbox(); - for node in tree.root.descendants() { - if let Some(rect) = node.calculate_bbox().and_then(|b| b.to_rect()) { - bbox = bbox.expand(rect); - } - } - - let canvas_rect = usvg::ScreenRect::new(0, 0, canvas.width(), canvas.height())?; - - // Compute the bbox after the transform is applied. - // We add a nice 5px border along the bounding box to - // be on the safe size. We also compute the intersection - // with the canvas rectangle - let svg_ts = usvg::Transform::new( - ts.sx.into(), - ts.kx.into(), - ts.ky.into(), - ts.sy.into(), - ts.tx.into(), - ts.ty.into(), - ); - let bbox = bbox.transform(&svg_ts)?.to_screen_rect(); - let bbox = usvg::ScreenRect::new( - bbox.left() - 5, - bbox.y() - 5, - bbox.width() + 10, - bbox.height() + 10, - )? - .fit_to_rect(canvas_rect); - - let mut pixmap = sk::Pixmap::new(bbox.width(), bbox.height())?; - - // We offset our transform so that the pixmap starts at the edge of the bbox. - let ts = ts.post_translate(-bbox.left() as f32, -bbox.top() as f32); - resvg::render(&tree, FitTo::Original, ts, pixmap.as_mut())?; - - canvas.draw_pixmap( - bbox.left(), - bbox.top(), - pixmap.as_ref(), - &sk::PixmapPaint::default(), - sk::Transform::identity(), - mask, - ); - - Some(()) -} - -/// Render a bitmap glyph into the canvas. -fn render_bitmap_glyph( - canvas: &mut sk::Pixmap, - ts: sk::Transform, - mask: Option<&sk::Mask>, - text: &TextItem, - id: GlyphId, -) -> Option<()> { - let size = text.size.to_f32(); - let ppem = size * ts.sy; - let raster = text.font.ttf().glyph_raster_image(id, ppem as u16)?; - let image = Image::new(raster.data.into(), raster.format.into(), None).ok()?; - - // FIXME: Vertical alignment isn't quite right for Apple Color Emoji, - // and maybe also for Noto Color Emoji. And: Is the size calculation - // correct? - let h = text.size; - let w = (image.width() as f64 / image.height() as f64) * h; - let dx = (raster.x as f32) / (image.width() as f32) * size; - let dy = (raster.y as f32) / (image.height() as f32) * size; - let ts = ts.pre_translate(dx, -size - dy); - render_image(canvas, ts, mask, &image, Size::new(w, h)) -} - -/// Render an outline glyph into the canvas. This is the "normal" case. -fn render_outline_glyph( - canvas: &mut sk::Pixmap, - ts: sk::Transform, - mask: Option<&sk::Mask>, - text: &TextItem, - id: GlyphId, -) -> Option<()> { - let ppem = text.size.to_f32() * ts.sy; - - // Render a glyph directly as a path. This only happens when the fast glyph - // rasterization can't be used due to very large text size or weird - // scale/skewing transforms. - if ppem > 100.0 || ts.kx != 0.0 || ts.ky != 0.0 || ts.sx != ts.sy { - let path = { - let mut builder = WrappedPathBuilder(sk::PathBuilder::new()); - text.font.ttf().outline_glyph(id, &mut builder)?; - builder.0.finish()? - }; - - let paint = (&text.fill).into(); - let rule = sk::FillRule::default(); - - // Flip vertically because font design coordinate - // system is Y-up. - let scale = text.size.to_f32() / text.font.units_per_em() as f32; - let ts = ts.pre_scale(scale, -scale); - canvas.fill_path(&path, &paint, rule, ts, mask); - return Some(()); - } - - // Rasterize the glyph with `pixglyph`. - #[comemo::memoize] - fn rasterize( - font: &Font, - id: GlyphId, - x: u32, - y: u32, - size: u32, - ) -> Option<Arc<Bitmap>> { - let glyph = pixglyph::Glyph::load(font.ttf(), id)?; - Some(Arc::new(glyph.rasterize( - f32::from_bits(x), - f32::from_bits(y), - f32::from_bits(size), - ))) - } - - // Try to retrieve a prepared glyph or prepare it from scratch if it - // doesn't exist, yet. - let bitmap = - rasterize(&text.font, id, ts.tx.to_bits(), ts.ty.to_bits(), ppem.to_bits())?; - - // If we have a clip mask we first render to a pixmap that we then blend - // with our canvas - if mask.is_some() { - let mw = bitmap.width; - let mh = bitmap.height; - - let Paint::Solid(color) = text.fill; - let c = color.to_rgba(); - - // Pad the pixmap with 1 pixel in each dimension so that we do - // not get any problem with floating point errors along their border - let mut pixmap = sk::Pixmap::new(mw + 2, mh + 2)?; - for x in 0..mw { - for y in 0..mh { - let alpha = bitmap.coverage[(y * mw + x) as usize]; - let color = sk::ColorU8::from_rgba(c.r, c.g, c.b, alpha).premultiply(); - pixmap.pixels_mut()[((y + 1) * (mw + 2) + (x + 1)) as usize] = color; - } - } - - let left = bitmap.left; - let top = bitmap.top; - - canvas.draw_pixmap( - left - 1, - top - 1, - pixmap.as_ref(), - &sk::PixmapPaint::default(), - sk::Transform::identity(), - mask, - ); - } else { - let cw = canvas.width() as i32; - let ch = canvas.height() as i32; - let mw = bitmap.width as i32; - let mh = bitmap.height as i32; - - // Determine the pixel bounding box that we actually need to draw. - let left = bitmap.left; - let right = left + mw; - let top = bitmap.top; - let bottom = top + mh; - - // Premultiply the text color. - let Paint::Solid(color) = text.fill; - let c = color.to_rgba(); - let color = sk::ColorU8::from_rgba(c.r, c.g, c.b, 255).premultiply().get(); - - // Blend the glyph bitmap with the existing pixels on the canvas. - let pixels = bytemuck::cast_slice_mut::<u8, u32>(canvas.data_mut()); - for x in left.clamp(0, cw)..right.clamp(0, cw) { - for y in top.clamp(0, ch)..bottom.clamp(0, ch) { - let ai = ((y - top) * mw + (x - left)) as usize; - let cov = bitmap.coverage[ai]; - if cov == 0 { - continue; - } - - let pi = (y * cw + x) as usize; - if cov == 255 { - pixels[pi] = color; - continue; - } - - let applied = alpha_mul(color, cov as u32); - pixels[pi] = blend_src_over(applied, pixels[pi]); - } - } - } - - Some(()) -} - -/// Render a geometrical shape into the canvas. -fn render_shape( - canvas: &mut sk::Pixmap, - ts: sk::Transform, - mask: Option<&sk::Mask>, - shape: &Shape, -) -> Option<()> { - let path = match shape.geometry { - Geometry::Line(target) => { - let mut builder = sk::PathBuilder::new(); - builder.line_to(target.x.to_f32(), target.y.to_f32()); - builder.finish()? - } - Geometry::Rect(size) => { - let w = size.x.to_f32(); - let h = size.y.to_f32(); - let rect = sk::Rect::from_xywh(0.0, 0.0, w, h)?; - sk::PathBuilder::from_rect(rect) - } - Geometry::Path(ref path) => convert_path(path)?, - }; - - if let Some(fill) = &shape.fill { - let mut paint: sk::Paint = fill.into(); - if matches!(shape.geometry, Geometry::Rect(_)) { - paint.anti_alias = false; - } - - let rule = sk::FillRule::default(); - canvas.fill_path(&path, &paint, rule, ts, mask); - } - - if let Some(Stroke { - paint, - thickness, - line_cap, - line_join, - dash_pattern, - miter_limit, - }) = &shape.stroke - { - let width = thickness.to_f32(); - - // Don't draw zero-pt stroke. - if width > 0.0 { - let dash = dash_pattern.as_ref().and_then(|pattern| { - // tiny-skia only allows dash patterns with an even number of elements, - // while pdf allows any number. - let pattern_len = pattern.array.len(); - let len = - if pattern_len % 2 == 1 { 2 * pattern_len } else { pattern_len }; - let dash_array = - pattern.array.iter().map(|l| l.to_f32()).cycle().take(len).collect(); - - sk::StrokeDash::new(dash_array, pattern.phase.to_f32()) - }); - let paint = paint.into(); - let stroke = sk::Stroke { - width, - line_cap: line_cap.into(), - line_join: line_join.into(), - dash, - miter_limit: miter_limit.0 as f32, - }; - canvas.stroke_path(&path, &paint, &stroke, ts, mask); - } - } - - Some(()) -} - -/// Convert a Typst path into a tiny-skia path. -fn convert_path(path: &geom::Path) -> Option<sk::Path> { - let mut builder = sk::PathBuilder::new(); - for elem in &path.0 { - match elem { - PathItem::MoveTo(p) => { - builder.move_to(p.x.to_f32(), p.y.to_f32()); - } - PathItem::LineTo(p) => { - builder.line_to(p.x.to_f32(), p.y.to_f32()); - } - PathItem::CubicTo(p1, p2, p3) => { - builder.cubic_to( - p1.x.to_f32(), - p1.y.to_f32(), - p2.x.to_f32(), - p2.y.to_f32(), - p3.x.to_f32(), - p3.y.to_f32(), - ); - } - PathItem::ClosePath => { - builder.close(); - } - }; - } - builder.finish() -} - -/// Render a raster or SVG image into the canvas. -fn render_image( - canvas: &mut sk::Pixmap, - ts: sk::Transform, - mask: Option<&sk::Mask>, - image: &Image, - size: Size, -) -> Option<()> { - let view_width = size.x.to_f32(); - let view_height = size.y.to_f32(); - - // For better-looking output, resize `image` to its final size before - // painting it to `canvas`. For the math, see: - // https://github.com/typst/typst/issues/1404#issuecomment-1598374652 - let theta = f32::atan2(-ts.kx, ts.sx); - - // To avoid division by 0, choose the one of { sin, cos } that is - // further from 0. - let prefer_sin = theta.sin().abs() > std::f32::consts::FRAC_1_SQRT_2; - let scale_x = - f32::abs(if prefer_sin { ts.kx / theta.sin() } else { ts.sx / theta.cos() }); - - let aspect = (image.width() as f32) / (image.height() as f32); - let w = (scale_x * view_width.max(aspect * view_height)).ceil() as u32; - let h = ((w as f32) / aspect).ceil() as u32; - - let pixmap = scaled_texture(image, w, h)?; - let paint_scale_x = view_width / pixmap.width() as f32; - let paint_scale_y = view_height / pixmap.height() as f32; - - let paint = sk::Paint { - shader: sk::Pattern::new( - (*pixmap).as_ref(), - sk::SpreadMode::Pad, - sk::FilterQuality::Nearest, - 1.0, - sk::Transform::from_scale(paint_scale_x, paint_scale_y), - ), - ..Default::default() - }; - - let rect = sk::Rect::from_xywh(0.0, 0.0, view_width, view_height)?; - canvas.fill_rect(rect, &paint, ts, mask); - - Some(()) -} - -/// Prepare a texture for an image at a scaled size. -#[comemo::memoize] -fn scaled_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> { - let mut pixmap = sk::Pixmap::new(w, h)?; - match image.decoded().as_ref() { - DecodedImage::Raster(dynamic, _, _) => { - let downscale = w < image.width(); - let filter = - if downscale { FilterType::Lanczos3 } else { FilterType::CatmullRom }; - let buf = dynamic.resize(w, h, filter); - for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) { - let Rgba([r, g, b, a]) = src; - *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply(); - } - } - DecodedImage::Svg(tree) => { - resvg::render( - tree, - FitTo::Size(w, h), - sk::Transform::identity(), - pixmap.as_mut(), - )?; - } - } - Some(Arc::new(pixmap)) -} - -impl From<Transform> for sk::Transform { - fn from(transform: Transform) -> Self { - let Transform { sx, ky, kx, sy, tx, ty } = transform; - sk::Transform::from_row( - sx.get() as _, - ky.get() as _, - kx.get() as _, - sy.get() as _, - tx.to_f32(), - ty.to_f32(), - ) - } -} - -impl From<&Paint> for sk::Paint<'static> { - fn from(paint: &Paint) -> Self { - let mut sk_paint = sk::Paint::default(); - let Paint::Solid(color) = *paint; - sk_paint.set_color(color.into()); - sk_paint.anti_alias = true; - sk_paint - } -} - -impl From<Color> for sk::Color { - fn from(color: Color) -> Self { - let c = color.to_rgba(); - sk::Color::from_rgba8(c.r, c.g, c.b, c.a) - } -} - -impl From<&LineCap> for sk::LineCap { - fn from(line_cap: &LineCap) -> Self { - match line_cap { - LineCap::Butt => sk::LineCap::Butt, - LineCap::Round => sk::LineCap::Round, - LineCap::Square => sk::LineCap::Square, - } - } -} - -impl From<&LineJoin> for sk::LineJoin { - fn from(line_join: &LineJoin) -> Self { - match line_join { - LineJoin::Miter => sk::LineJoin::Miter, - LineJoin::Round => sk::LineJoin::Round, - LineJoin::Bevel => sk::LineJoin::Bevel, - } - } -} - -/// Allows to build tiny-skia paths from glyph outlines. -struct WrappedPathBuilder(sk::PathBuilder); - -impl OutlineBuilder for WrappedPathBuilder { - fn move_to(&mut self, x: f32, y: f32) { - self.0.move_to(x, y); - } - - fn line_to(&mut self, x: f32, y: f32) { - self.0.line_to(x, y); - } - - fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { - self.0.quad_to(x1, y1, x, y); - } - - fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { - self.0.cubic_to(x1, y1, x2, y2, x, y); - } - - fn close(&mut self) { - self.0.close(); - } -} - -/// Additional methods for [`Length`]. -trait AbsExt { - /// Convert to a number of points as f32. - fn to_f32(self) -> f32; -} - -impl AbsExt for Abs { - fn to_f32(self) -> f32 { - self.to_pt() as f32 - } -} - -// Alpha multiplication and blending are ported from: -// https://skia.googlesource.com/skia/+/refs/heads/main/include/core/SkColorPriv.h - -/// Blends two premulitplied, packed 32-bit RGBA colors. Alpha channel must be -/// in the 8 high bits. -fn blend_src_over(src: u32, dst: u32) -> u32 { - src + alpha_mul(dst, 256 - (src >> 24)) -} - -/// Alpha multiply a color. -fn alpha_mul(color: u32, scale: u32) -> u32 { - let mask = 0xff00ff; - let rb = ((color & mask) * scale) >> 8; - let ag = ((color >> 8) & mask) * scale; - (rb & mask) | (ag & !mask) -} diff --git a/src/file.rs b/src/file.rs deleted file mode 100644 index 8aaa746b..00000000 --- a/src/file.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! File and package management. - -use std::collections::HashMap; -use std::fmt::{self, Debug, Display, Formatter}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::RwLock; - -use ecow::{eco_format, EcoString}; -use once_cell::sync::Lazy; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use crate::diag::{bail, FileError, StrResult}; -use crate::syntax::is_ident; -use crate::util::PathExt; - -/// The global package-path interner. -static INTERNER: Lazy<RwLock<Interner>> = - Lazy::new(|| RwLock::new(Interner { to_id: HashMap::new(), from_id: Vec::new() })); - -/// A package-path interner. -struct Interner { - to_id: HashMap<Pair, FileId>, - from_id: Vec<Pair>, -} - -/// An interned pair of a package specification and a path. -type Pair = &'static (Option<PackageSpec>, PathBuf); - -/// Identifies a file. -/// -/// This type is globally interned and thus cheap to copy, compare, and hash. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct FileId(u16); - -impl FileId { - /// Create a new interned file specification. - /// - /// The path must start with a `/` or this function will panic. - /// Note that the path is normalized before interning. - #[track_caller] - pub fn new(package: Option<PackageSpec>, path: &Path) -> Self { - assert_eq!( - path.components().next(), - Some(std::path::Component::RootDir), - "file path must be absolute within project or package: {}", - path.display(), - ); - - // Try to find an existing entry that we can reuse. - let pair = (package, path.normalize()); - if let Some(&id) = INTERNER.read().unwrap().to_id.get(&pair) { - return id; - } - - let mut interner = INTERNER.write().unwrap(); - let len = interner.from_id.len(); - if len >= usize::from(u16::MAX) { - panic!("too many file specifications"); - } - - // Create a new entry forever by leaking the pair. We can't leak more - // than 2^16 pair (and typically will leak a lot less), so its not a - // big deal. - let id = FileId(len as u16); - let leaked = Box::leak(Box::new(pair)); - interner.to_id.insert(leaked, id); - interner.from_id.push(leaked); - id - } - - /// Get an id that does not identify any real file. - pub const fn detached() -> Self { - Self(u16::MAX) - } - - /// Whether the id is the detached. - pub const fn is_detached(self) -> bool { - self.0 == Self::detached().0 - } - - /// The package the file resides in, if any. - pub fn package(&self) -> Option<&'static PackageSpec> { - if self.is_detached() { - None - } else { - self.pair().0.as_ref() - } - } - - /// The absolute and normalized path to the file _within_ the project or - /// package. - pub fn path(&self) -> &'static Path { - if self.is_detached() { - Path::new("/detached.typ") - } else { - &self.pair().1 - } - } - - /// Resolve a file location relative to this file. - pub fn join(self, path: &str) -> StrResult<Self> { - if self.is_detached() { - bail!("cannot access file system from here"); - } - - let package = self.package().cloned(); - let base = self.path(); - Ok(if let Some(parent) = base.parent() { - Self::new(package, &parent.join(path)) - } else { - Self::new(package, Path::new(path)) - }) - } - - /// Construct from a raw number. - pub(crate) const fn from_u16(v: u16) -> Self { - Self(v) - } - - /// Extract the raw underlying number. - pub(crate) const fn as_u16(self) -> u16 { - self.0 - } - - /// Get the static pair. - fn pair(&self) -> Pair { - INTERNER.read().unwrap().from_id[usize::from(self.0)] - } -} - -impl Display for FileId { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let path = self.path().display(); - match self.package() { - Some(package) => write!(f, "{package}{path}"), - None => write!(f, "{path}"), - } - } -} - -impl Debug for FileId { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Display::fmt(self, f) - } -} - -/// Identifies a package. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct PackageSpec { - /// The namespace the package lives in. - pub namespace: EcoString, - /// The name of the package within its namespace. - pub name: EcoString, - /// The package's version. - pub version: Version, -} - -impl FromStr for PackageSpec { - type Err = EcoString; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - let mut s = unscanny::Scanner::new(s); - if !s.eat_if('@') { - bail!("package specification must start with '@'"); - } - - let namespace = s.eat_until('/'); - if namespace.is_empty() { - bail!("package specification is missing namespace"); - } else if !is_ident(namespace) { - bail!("`{namespace}` is not a valid package namespace"); - } - - s.eat_if('/'); - - let name = s.eat_until(':'); - if name.is_empty() { - bail!("package specification is missing name"); - } else if !is_ident(name) { - bail!("`{name}` is not a valid package name"); - } - - s.eat_if(':'); - - let version = s.after(); - if version.is_empty() { - bail!("package specification is missing version"); - } - - Ok(Self { - namespace: namespace.into(), - name: name.into(), - version: version.parse()?, - }) - } -} - -impl Display for PackageSpec { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "@{}/{}:{}", self.namespace, self.name, self.version) - } -} - -/// A package's version. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Version { - /// The package's major version. - pub major: u32, - /// The package's minor version. - pub minor: u32, - /// The package's patch version. - pub patch: u32, -} - -impl FromStr for Version { - type Err = EcoString; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - let mut parts = s.split('.'); - let mut next = |kind| { - let Some(part) = parts.next().filter(|s| !s.is_empty()) else { - bail!("version number is missing {kind} version"); - }; - part.parse::<u32>() - .map_err(|_| eco_format!("`{part}` is not a valid {kind} version")) - }; - - let major = next("major")?; - let minor = next("minor")?; - let patch = next("patch")?; - if let Some(rest) = parts.next() { - bail!("version number has unexpected fourth component: `{rest}`"); - } - - Ok(Self { major, minor, patch }) - } -} - -impl Display for Version { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}.{}.{}", self.major, self.minor, self.patch) - } -} - -impl Serialize for Version { - fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { - s.collect_str(self) - } -} - -impl<'de> Deserialize<'de> for Version { - fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> { - let string = EcoString::deserialize(d)?; - string.parse().map_err(serde::de::Error::custom) - } -} - -/// A parsed package manifest. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub struct PackageManifest { - /// Details about the package itself. - pub package: PackageInfo, -} - -impl PackageManifest { - /// Parse the manifest from raw bytes. - pub fn parse(bytes: &[u8]) -> StrResult<Self> { - let string = std::str::from_utf8(bytes).map_err(FileError::from)?; - toml::from_str(string).map_err(|err| { - eco_format!("package manifest is malformed: {}", err.message()) - }) - } - - /// Ensure that this manifest is indeed for the specified package. - pub fn validate(&self, spec: &PackageSpec) -> StrResult<()> { - if self.package.name != spec.name { - bail!("package manifest contains mismatched name `{}`", self.package.name); - } - - if self.package.version != spec.version { - bail!( - "package manifest contains mismatched version {}", - self.package.version - ); - } - - Ok(()) - } -} - -/// The `package` key in the manifest. -/// -/// More fields are specified, but they are not relevant to the compiler. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub struct PackageInfo { - /// The name of the package within its namespace. - pub name: EcoString, - /// The package's version. - pub version: Version, - /// The path of the entrypoint into the package. - pub entrypoint: EcoString, -} diff --git a/src/font/book.rs b/src/font/book.rs deleted file mode 100644 index 2b7742bf..00000000 --- a/src/font/book.rs +++ /dev/null @@ -1,546 +0,0 @@ -use std::cmp::Reverse; -use std::collections::BTreeMap; - -use serde::{Deserialize, Serialize}; -use ttf_parser::{name_id, PlatformId, Tag}; -use unicode_segmentation::UnicodeSegmentation; - -use super::{Font, FontStretch, FontStyle, FontVariant, FontWeight}; - -/// Metadata about a collection of fonts. -#[derive(Default, Clone, Hash)] -pub struct FontBook { - /// Maps from lowercased family names to font indices. - families: BTreeMap<String, Vec<usize>>, - /// Metadata about each font in the collection. - infos: Vec<FontInfo>, -} - -impl FontBook { - /// Create a new, empty font book. - pub fn new() -> Self { - Self { families: BTreeMap::new(), infos: vec![] } - } - - /// Create a font book for a collection of fonts. - pub fn from_fonts<'a>(fonts: impl IntoIterator<Item = &'a Font>) -> Self { - let mut book = Self::new(); - for font in fonts { - book.push(font.info().clone()); - } - book - } - - /// Insert metadata into the font book. - pub fn push(&mut self, info: FontInfo) { - let index = self.infos.len(); - let family = info.family.to_lowercase(); - self.families.entry(family).or_default().push(index); - self.infos.push(info); - } - - /// Get the font info for the given index. - pub fn info(&self, index: usize) -> Option<&FontInfo> { - self.infos.get(index) - } - - /// An ordered iterator over all font families this book knows and details - /// about the fonts that are part of them. - pub fn families( - &self, - ) -> impl Iterator<Item = (&str, impl Iterator<Item = &FontInfo>)> + '_ { - // Since the keys are lowercased, we instead use the family field of the - // first face's info. - self.families.values().map(|ids| { - let family = self.infos[ids[0]].family.as_str(); - let infos = ids.iter().map(|&id| &self.infos[id]); - (family, infos) - }) - } - - /// Try to find a font from the given `family` that matches the given - /// `variant` as closely as possible. - /// - /// The `family` should be all lowercase. - pub fn select(&self, family: &str, variant: FontVariant) -> Option<usize> { - let ids = self.families.get(family)?; - self.find_best_variant(None, variant, ids.iter().copied()) - } - - /// Iterate over all variants of a family. - pub fn select_family(&self, family: &str) -> impl Iterator<Item = usize> + '_ { - self.families - .get(family) - .map(|vec| vec.as_slice()) - .unwrap_or_default() - .iter() - .copied() - } - - /// Try to find and load a fallback font that - /// - is as close as possible to the font `like` (if any) - /// - is as close as possible to the given `variant` - /// - is suitable for shaping the given `text` - pub fn select_fallback( - &self, - like: Option<&FontInfo>, - variant: FontVariant, - text: &str, - ) -> Option<usize> { - // Find the fonts that contain the text's first char ... - let c = text.chars().next()?; - let ids = self - .infos - .iter() - .enumerate() - .filter(|(_, info)| info.coverage.contains(c as u32)) - .map(|(index, _)| index); - - // ... and find the best variant among them. - self.find_best_variant(like, variant, ids) - } - - /// Find the font in the passed iterator that - /// - is closest to the font `like` (if any) - /// - is closest to the given `variant` - /// - /// To do that we compute a key for all variants and select the one with the - /// minimal key. This key prioritizes: - /// - If `like` is some other font: - /// - Are both fonts (not) monospaced? - /// - Do both fonts (not) have serifs? - /// - How many words do the families share in their prefix? E.g. "Noto - /// Sans" and "Noto Sans Arabic" share two words, whereas "IBM Plex - /// Arabic" shares none with "Noto Sans", so prefer "Noto Sans Arabic" - /// if `like` is "Noto Sans". In case there are two equally good - /// matches, we prefer the shorter one because it is less special (e.g. - /// if `like` is "Noto Sans Arabic", we prefer "Noto Sans" over "Noto - /// Sans CJK HK".) - /// - The style (normal / italic / oblique). If we want italic or oblique - /// but it doesn't exist, the other one of the two is still better than - /// normal. - /// - The absolute distance to the target stretch. - /// - The absolute distance to the target weight. - fn find_best_variant( - &self, - like: Option<&FontInfo>, - variant: FontVariant, - ids: impl IntoIterator<Item = usize>, - ) -> Option<usize> { - let mut best = None; - let mut best_key = None; - - for id in ids { - let current = &self.infos[id]; - let key = ( - like.map(|like| { - ( - current.flags.contains(FontFlags::MONOSPACE) - != like.flags.contains(FontFlags::MONOSPACE), - current.flags.contains(FontFlags::SERIF) - != like.flags.contains(FontFlags::SERIF), - Reverse(shared_prefix_words(¤t.family, &like.family)), - current.family.len(), - ) - }), - current.variant.style.distance(variant.style), - current.variant.stretch.distance(variant.stretch), - current.variant.weight.distance(variant.weight), - ); - - if best_key.map_or(true, |b| key < b) { - best = Some(id); - best_key = Some(key); - } - } - - best - } -} - -/// Properties of a single font. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub struct FontInfo { - /// The typographic font family this font is part of. - pub family: String, - /// Properties that distinguish this font from other fonts in the same - /// family. - pub variant: FontVariant, - /// Properties of the font. - pub flags: FontFlags, - /// The unicode coverage of the font. - pub coverage: Coverage, -} - -bitflags::bitflags! { - /// Bitflags describing characteristics of a font. - #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] - #[derive(Serialize, Deserialize)] - #[serde(transparent)] - pub struct FontFlags: u32 { - /// All glyphs have the same width. - const MONOSPACE = 1 << 0; - /// Glyphs have short strokes at their stems. - const SERIF = 1 << 1; - } -} - -impl FontInfo { - /// Compute metadata for all fonts in the given data. - pub fn iter(data: &[u8]) -> impl Iterator<Item = FontInfo> + '_ { - let count = ttf_parser::fonts_in_collection(data).unwrap_or(1); - (0..count).filter_map(move |index| { - let ttf = ttf_parser::Face::parse(data, index).ok()?; - Self::from_ttf(&ttf) - }) - } - - /// Compute metadata for a single ttf-parser face. - pub(super) fn from_ttf(ttf: &ttf_parser::Face) -> Option<Self> { - // We cannot use Name ID 16 "Typographic Family", because for some - // fonts it groups together more than just Style / Weight / Stretch - // variants (e.g. Display variants of Noto fonts) and then some - // variants become inaccessible from Typst. And even though the - // fsSelection bit WWS should help us decide whether that is the - // case, it's wrong for some fonts (e.g. for certain variants of "Noto - // Sans Display"). - // - // So, instead we use Name ID 1 "Family" and trim many common - // suffixes for which know that they just describe styling (e.g. - // "ExtraBold"). - // - // Also, for Noto fonts we use Name ID 4 "Full Name" instead, - // because Name ID 1 "Family" sometimes contains "Display" and - // sometimes doesn't for the Display variants and that mixes things - // up. - let family = { - let mut family = find_name(ttf, name_id::FAMILY)?; - if family.starts_with("Noto") - || family.starts_with("NewCM") - || family.starts_with("NewComputerModern") - { - family = find_name(ttf, name_id::FULL_NAME)?; - } - typographic_family(&family).to_string() - }; - - let variant = { - let mut full = find_name(ttf, name_id::FULL_NAME).unwrap_or_default(); - full.make_ascii_lowercase(); - - // Some fonts miss the relevant bits for italic or oblique, so - // we also try to infer that from the full name. - let italic = ttf.is_italic() || full.contains("italic"); - let oblique = - ttf.is_oblique() || full.contains("oblique") || full.contains("slanted"); - - let style = match (italic, oblique) { - (false, false) => FontStyle::Normal, - (true, _) => FontStyle::Italic, - (_, true) => FontStyle::Oblique, - }; - - let weight = { - let mut number = ttf.weight().to_number(); - if (family.starts_with("NewCM") - || family.starts_with("New Computer Modern")) - && full.contains("book") - { - number += 50; - } - FontWeight::from_number(number) - }; - - let stretch = FontStretch::from_number(ttf.width().to_number()); - FontVariant { style, weight, stretch } - }; - - // Determine the unicode coverage. - let mut codepoints = vec![]; - for subtable in ttf.tables().cmap.into_iter().flat_map(|table| table.subtables) { - if subtable.is_unicode() { - subtable.codepoints(|c| codepoints.push(c)); - } - } - - let mut flags = FontFlags::empty(); - flags.set(FontFlags::MONOSPACE, ttf.is_monospaced()); - - // Determine whether this is a serif or sans-serif font. - if let Some(panose) = ttf - .raw_face() - .table(Tag::from_bytes(b"OS/2")) - .and_then(|os2| os2.get(32..45)) - { - if matches!(panose, [2, 2..=10, ..]) { - flags.insert(FontFlags::SERIF); - } - } - - Some(FontInfo { - family, - variant, - flags, - coverage: Coverage::from_vec(codepoints), - }) - } -} - -/// Try to find and decode the name with the given id. -pub(super) fn find_name(ttf: &ttf_parser::Face, name_id: u16) -> Option<String> { - ttf.names().into_iter().find_map(|entry| { - if entry.name_id == name_id { - if let Some(string) = entry.to_string() { - return Some(string); - } - - if entry.platform_id == PlatformId::Macintosh && entry.encoding_id == 0 { - return Some(decode_mac_roman(entry.name)); - } - } - - None - }) -} - -/// Decode mac roman encoded bytes into a string. -fn decode_mac_roman(coded: &[u8]) -> String { - #[rustfmt::skip] - const TABLE: [char; 128] = [ - 'Ä', 'Å', 'Ç', 'É', 'Ñ', 'Ö', 'Ü', 'á', 'à', 'â', 'ä', 'ã', 'å', 'ç', 'é', 'è', - 'ê', 'ë', 'í', 'ì', 'î', 'ï', 'ñ', 'ó', 'ò', 'ô', 'ö', 'õ', 'ú', 'ù', 'û', 'ü', - '†', '°', '¢', '£', '§', '•', '¶', 'ß', '®', '©', '™', '´', '¨', '≠', 'Æ', 'Ø', - '∞', '±', '≤', '≥', '¥', 'µ', '∂', '∑', '∏', 'π', '∫', 'ª', 'º', 'Ω', 'æ', 'ø', - '¿', '¡', '¬', '√', 'ƒ', '≈', '∆', '«', '»', '…', '\u{a0}', 'À', 'Ã', 'Õ', 'Œ', 'œ', - '–', '—', '“', '”', '‘', '’', '÷', '◊', 'ÿ', 'Ÿ', '⁄', '€', '‹', '›', 'fi', 'fl', - '‡', '·', '‚', '„', '‰', 'Â', 'Ê', 'Á', 'Ë', 'È', 'Í', 'Î', 'Ï', 'Ì', 'Ó', 'Ô', - '\u{f8ff}', 'Ò', 'Ú', 'Û', 'Ù', 'ı', 'ˆ', '˜', '¯', '˘', '˙', '˚', '¸', '˝', '˛', 'ˇ', - ]; - - fn char_from_mac_roman(code: u8) -> char { - if code < 128 { - code as char - } else { - TABLE[(code - 128) as usize] - } - } - - coded.iter().copied().map(char_from_mac_roman).collect() -} - -/// Trim style naming from a family name and fix bad names. -fn typographic_family(mut family: &str) -> &str { - // Separators between names, modifiers and styles. - const SEPARATORS: [char; 3] = [' ', '-', '_']; - - // Modifiers that can appear in combination with suffixes. - const MODIFIERS: &[&str] = - &["extra", "ext", "ex", "x", "semi", "sem", "sm", "demi", "dem", "ultra"]; - - // Style suffixes. - #[rustfmt::skip] - const SUFFIXES: &[&str] = &[ - "normal", "italic", "oblique", "slanted", - "thin", "th", "hairline", "light", "lt", "regular", "medium", "med", - "md", "bold", "bd", "demi", "extb", "black", "blk", "bk", "heavy", - "narrow", "condensed", "cond", "cn", "cd", "compressed", "expanded", "exp" - ]; - - let mut extra = [].as_slice(); - let newcm = family.starts_with("NewCM") || family.starts_with("NewComputerModern"); - if newcm { - extra = &["book"]; - } - - // Trim spacing and weird leading dots in Apple fonts. - family = family.trim().trim_start_matches('.'); - - // Lowercase the string so that the suffixes match case-insensitively. - let lower = family.to_ascii_lowercase(); - let mut len = usize::MAX; - let mut trimmed = lower.as_str(); - - // Trim style suffixes repeatedly. - while trimmed.len() < len { - len = trimmed.len(); - - // Find style suffix. - let mut t = trimmed; - let mut shortened = false; - while let Some(s) = SUFFIXES.iter().chain(extra).find_map(|s| t.strip_suffix(s)) { - shortened = true; - t = s; - } - - if !shortened { - break; - } - - // Strip optional separator. - if let Some(s) = t.strip_suffix(SEPARATORS) { - trimmed = s; - t = s; - } - - // Also allow an extra modifier, but apply it only if it is separated it - // from the text before it (to prevent false positives). - if let Some(t) = MODIFIERS.iter().find_map(|s| t.strip_suffix(s)) { - if let Some(stripped) = t.strip_suffix(SEPARATORS) { - trimmed = stripped; - } - } - } - - // Apply style suffix trimming. - family = &family[..len]; - - if newcm { - family = family.trim_end_matches("10"); - } - - // Fix bad names. - match family { - "Noto Sans Symbols2" => "Noto Sans Symbols 2", - "NewComputerModern" => "New Computer Modern", - "NewComputerModernMono" => "New Computer Modern Mono", - "NewComputerModernSans" => "New Computer Modern Sans", - "NewComputerModernMath" => "New Computer Modern Math", - "NewCMUncial" | "NewComputerModernUncial" => "New Computer Modern Uncial", - other => other, - } -} - -/// How many words the two strings share in their prefix. -fn shared_prefix_words(left: &str, right: &str) -> usize { - left.unicode_words() - .zip(right.unicode_words()) - .take_while(|(l, r)| l == r) - .count() -} - -/// A compactly encoded set of codepoints. -/// -/// The set is represented by alternating specifications of how many codepoints -/// are not in the set and how many are in the set. -/// -/// For example, for the set `{2, 3, 4, 9, 10, 11, 15, 18, 19}`, there are: -/// - 2 codepoints not inside (0, 1) -/// - 3 codepoints inside (2, 3, 4) -/// - 4 codepoints not inside (5, 6, 7, 8) -/// - 3 codepoints inside (9, 10, 11) -/// - 3 codepoints not inside (12, 13, 14) -/// - 1 codepoint inside (15) -/// - 2 codepoints not inside (16, 17) -/// - 2 codepoints inside (18, 19) -/// -/// So the resulting encoding is `[2, 3, 4, 3, 3, 1, 2, 2]`. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Coverage(Vec<u32>); - -impl Coverage { - /// Encode a vector of codepoints. - pub fn from_vec(mut codepoints: Vec<u32>) -> Self { - codepoints.sort(); - codepoints.dedup(); - - let mut runs = Vec::new(); - let mut next = 0; - - for c in codepoints { - if let Some(run) = runs.last_mut().filter(|_| c == next) { - *run += 1; - } else { - runs.push(c - next); - runs.push(1); - } - - next = c + 1; - } - - Self(runs) - } - - /// Whether the codepoint is covered. - pub fn contains(&self, c: u32) -> bool { - let mut inside = false; - let mut cursor = 0; - - for &run in &self.0 { - if (cursor..cursor + run).contains(&c) { - return inside; - } - cursor += run; - inside = !inside; - } - - false - } - - /// Iterate over all covered codepoints. - pub fn iter(&self) -> impl Iterator<Item = u32> + '_ { - let mut inside = false; - let mut cursor = 0; - self.0.iter().flat_map(move |run| { - let range = if inside { cursor..cursor + run } else { 0..0 }; - inside = !inside; - cursor += run; - range - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_trim_styles() { - assert_eq!(typographic_family("Atma Light"), "Atma"); - assert_eq!(typographic_family("eras bold"), "eras"); - assert_eq!(typographic_family("footlight mt light"), "footlight mt"); - assert_eq!(typographic_family("times new roman"), "times new roman"); - assert_eq!(typographic_family("noto sans mono cond sembd"), "noto sans mono"); - assert_eq!(typographic_family("noto serif SEMCOND sembd"), "noto serif"); - assert_eq!(typographic_family("crimson text"), "crimson text"); - assert_eq!(typographic_family("footlight light"), "footlight"); - assert_eq!(typographic_family("Noto Sans"), "Noto Sans"); - assert_eq!(typographic_family("Noto Sans Light"), "Noto Sans"); - assert_eq!(typographic_family("Noto Sans Semicondensed Heavy"), "Noto Sans"); - assert_eq!(typographic_family("Familx"), "Familx"); - assert_eq!(typographic_family("Font Ultra"), "Font Ultra"); - assert_eq!(typographic_family("Font Ultra Bold"), "Font"); - } - - #[test] - fn test_coverage() { - #[track_caller] - fn test(set: &[u32], runs: &[u32]) { - let coverage = Coverage::from_vec(set.to_vec()); - assert_eq!(coverage.0, runs); - - let max = 5 + set.iter().copied().max().unwrap_or_default(); - for c in 0..max { - assert_eq!(set.contains(&c), coverage.contains(c)); - } - } - - test(&[], &[]); - test(&[0], &[0, 1]); - test(&[1], &[1, 1]); - test(&[0, 1], &[0, 2]); - test(&[0, 1, 3], &[0, 2, 1, 1]); - test( - // {2, 3, 4, 9, 10, 11, 15, 18, 19} - &[18, 19, 2, 4, 9, 11, 15, 3, 3, 10], - &[2, 3, 4, 3, 3, 1, 2, 2], - ) - } - - #[test] - fn test_coverage_iter() { - let codepoints = vec![2, 3, 7, 8, 9, 14, 15, 19, 21]; - let coverage = Coverage::from_vec(codepoints.clone()); - assert_eq!(coverage.iter().collect::<Vec<_>>(), codepoints); - } -} diff --git a/src/font/mod.rs b/src/font/mod.rs deleted file mode 100644 index 2353e51c..00000000 --- a/src/font/mod.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! Font handling. - -mod book; -mod variant; - -pub use self::book::{Coverage, FontBook, FontFlags, FontInfo}; -pub use self::variant::{FontStretch, FontStyle, FontVariant, FontWeight}; - -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -use ttf_parser::GlyphId; - -use self::book::find_name; -use crate::eval::Cast; -use crate::geom::Em; -use crate::util::Bytes; - -/// An OpenType font. -/// -/// Values of this type are cheap to clone and hash. -#[derive(Clone)] -pub struct Font(Arc<Repr>); - -/// The internal representation of a font. -struct Repr { - /// The raw font data, possibly shared with other fonts from the same - /// collection. The vector's allocation must not move, because `ttf` points - /// into it using unsafe code. - data: Bytes, - /// The font's index in the buffer. - index: u32, - /// Metadata about the font. - info: FontInfo, - /// The font's metrics. - metrics: FontMetrics, - /// The underlying ttf-parser face. - ttf: ttf_parser::Face<'static>, - /// The underlying rustybuzz face. - rusty: rustybuzz::Face<'static>, -} - -impl Font { - /// Parse a font from data and collection index. - pub fn new(data: Bytes, index: u32) -> Option<Self> { - // Safety: - // - The slices's location is stable in memory: - // - We don't move the underlying vector - // - Nobody else can move it since we have a strong ref to the `Arc`. - // - The internal 'static lifetime is not leaked because its rewritten - // to the self-lifetime in `ttf()`. - let slice: &'static [u8] = - unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) }; - - let ttf = ttf_parser::Face::parse(slice, index).ok()?; - let rusty = rustybuzz::Face::from_slice(slice, index)?; - let metrics = FontMetrics::from_ttf(&ttf); - let info = FontInfo::from_ttf(&ttf)?; - - Some(Self(Arc::new(Repr { data, index, info, metrics, ttf, rusty }))) - } - - /// Parse all fonts in the given data. - pub fn iter(data: Bytes) -> impl Iterator<Item = Self> { - let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1); - (0..count).filter_map(move |index| Self::new(data.clone(), index)) - } - - /// The underlying buffer. - pub fn data(&self) -> &Bytes { - &self.0.data - } - - /// The font's index in the buffer. - pub fn index(&self) -> u32 { - self.0.index - } - - /// The font's metadata. - pub fn info(&self) -> &FontInfo { - &self.0.info - } - - /// The font's metrics. - pub fn metrics(&self) -> &FontMetrics { - &self.0.metrics - } - - /// The number of font units per one em. - pub fn units_per_em(&self) -> f64 { - self.0.metrics.units_per_em - } - - /// Convert from font units to an em length. - pub fn to_em(&self, units: impl Into<f64>) -> Em { - Em::from_units(units, self.units_per_em()) - } - - /// Look up the horizontal advance width of a glyph. - pub fn advance(&self, glyph: u16) -> Option<Em> { - self.0 - .ttf - .glyph_hor_advance(GlyphId(glyph)) - .map(|units| self.to_em(units)) - } - - /// Lookup a name by id. - pub fn find_name(&self, id: u16) -> Option<String> { - find_name(&self.0.ttf, id) - } - - /// A reference to the underlying `ttf-parser` face. - pub fn ttf(&self) -> &ttf_parser::Face<'_> { - // We can't implement Deref because that would leak the - // internal 'static lifetime. - &self.0.ttf - } - - /// A reference to the underlying `rustybuzz` face. - pub fn rusty(&self) -> &rustybuzz::Face<'_> { - // We can't implement Deref because that would leak the - // internal 'static lifetime. - &self.0.rusty - } -} - -impl Hash for Font { - fn hash<H: Hasher>(&self, state: &mut H) { - self.0.data.hash(state); - self.0.index.hash(state); - } -} - -impl Debug for Font { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Font({})", self.info().family) - } -} - -impl Eq for Font {} - -impl PartialEq for Font { - fn eq(&self, other: &Self) -> bool { - self.0.data == other.0.data && self.0.index == other.0.index - } -} - -/// Metrics of a font. -#[derive(Debug, Copy, Clone)] -pub struct FontMetrics { - /// How many font units represent one em unit. - pub units_per_em: f64, - /// The distance from the baseline to the typographic ascender. - pub ascender: Em, - /// The approximate height of uppercase letters. - pub cap_height: Em, - /// The approximate height of non-ascending lowercase letters. - pub x_height: Em, - /// The distance from the baseline to the typographic descender. - pub descender: Em, - /// Recommended metrics for a strikethrough line. - pub strikethrough: LineMetrics, - /// Recommended metrics for an underline. - pub underline: LineMetrics, - /// Recommended metrics for an overline. - pub overline: LineMetrics, -} - -impl FontMetrics { - /// Extract the font's metrics. - pub fn from_ttf(ttf: &ttf_parser::Face) -> Self { - let units_per_em = f64::from(ttf.units_per_em()); - let to_em = |units| Em::from_units(units, units_per_em); - - let ascender = to_em(ttf.typographic_ascender().unwrap_or(ttf.ascender())); - let cap_height = ttf.capital_height().filter(|&h| h > 0).map_or(ascender, to_em); - let x_height = ttf.x_height().filter(|&h| h > 0).map_or(ascender, to_em); - let descender = to_em(ttf.typographic_descender().unwrap_or(ttf.descender())); - let strikeout = ttf.strikeout_metrics(); - let underline = ttf.underline_metrics(); - - let strikethrough = LineMetrics { - position: strikeout.map_or(Em::new(0.25), |s| to_em(s.position)), - thickness: strikeout - .or(underline) - .map_or(Em::new(0.06), |s| to_em(s.thickness)), - }; - - let underline = LineMetrics { - position: underline.map_or(Em::new(-0.2), |s| to_em(s.position)), - thickness: underline - .or(strikeout) - .map_or(Em::new(0.06), |s| to_em(s.thickness)), - }; - - let overline = LineMetrics { - position: cap_height + Em::new(0.1), - thickness: underline.thickness, - }; - - Self { - units_per_em, - ascender, - cap_height, - x_height, - descender, - strikethrough, - underline, - overline, - } - } - - /// Look up a vertical metric. - pub fn vertical(&self, metric: VerticalFontMetric) -> Em { - match metric { - VerticalFontMetric::Ascender => self.ascender, - VerticalFontMetric::CapHeight => self.cap_height, - VerticalFontMetric::XHeight => self.x_height, - VerticalFontMetric::Baseline => Em::zero(), - VerticalFontMetric::Descender => self.descender, - } - } -} - -/// Metrics for a decorative line. -#[derive(Debug, Copy, Clone)] -pub struct LineMetrics { - /// The vertical offset of the line from the baseline. Positive goes - /// upwards, negative downwards. - pub position: Em, - /// The thickness of the line. - pub thickness: Em, -} - -/// Identifies a vertical metric of a font. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum VerticalFontMetric { - /// The font's ascender, which typically exceeds the height of all glyphs. - Ascender, - /// The approximate height of uppercase letters. - CapHeight, - /// The approximate height of non-ascending lowercase letters. - XHeight, - /// The baseline on which the letters rest. - Baseline, - /// The font's ascender, which typically exceeds the depth of all glyphs. - Descender, -} diff --git a/src/font/variant.rs b/src/font/variant.rs deleted file mode 100644 index d4508a59..00000000 --- a/src/font/variant.rs +++ /dev/null @@ -1,270 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; - -use serde::{Deserialize, Serialize}; - -use crate::eval::{cast, Cast, IntoValue}; -use crate::geom::Ratio; - -/// Properties that distinguish a font from other fonts in the same family. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize)] -pub struct FontVariant { - /// The style of the font (normal / italic / oblique). - pub style: FontStyle, - /// How heavy the font is (100 - 900). - pub weight: FontWeight, - /// How condensed or expanded the font is (0.5 - 2.0). - pub stretch: FontStretch, -} - -impl FontVariant { - /// Create a variant from its three components. - pub fn new(style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Self { - Self { style, weight, stretch } - } -} - -impl Debug for FontVariant { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{:?}-{:?}-{:?}", self.style, self.weight, self.stretch) - } -} - -/// The style of a font. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize, Cast)] -#[serde(rename_all = "kebab-case")] -pub enum FontStyle { - /// The default, typically upright style. - Normal, - /// A cursive style with custom letterform. - Italic, - /// Just a slanted version of the normal style. - Oblique, -} - -impl FontStyle { - /// The conceptual distance between the styles, expressed as a number. - pub fn distance(self, other: Self) -> u16 { - if self == other { - 0 - } else if self != Self::Normal && other != Self::Normal { - 1 - } else { - 2 - } - } -} - -impl Default for FontStyle { - fn default() -> Self { - Self::Normal - } -} - -/// The weight of a font. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize)] -#[serde(transparent)] -pub struct FontWeight(u16); - -impl FontWeight { - /// Thin weight (100). - pub const THIN: Self = Self(100); - - /// Extra light weight (200). - pub const EXTRALIGHT: Self = Self(200); - - /// Light weight (300). - pub const LIGHT: Self = Self(300); - - /// Regular weight (400). - pub const REGULAR: Self = Self(400); - - /// Medium weight (500). - pub const MEDIUM: Self = Self(500); - - /// Semibold weight (600). - pub const SEMIBOLD: Self = Self(600); - - /// Bold weight (700). - pub const BOLD: Self = Self(700); - - /// Extrabold weight (800). - pub const EXTRABOLD: Self = Self(800); - - /// Black weight (900). - pub const BLACK: Self = Self(900); - - /// Create a font weight from a number between 100 and 900, clamping it if - /// necessary. - pub fn from_number(weight: u16) -> Self { - Self(weight.max(100).min(900)) - } - - /// The number between 100 and 900. - pub fn to_number(self) -> u16 { - self.0 - } - - /// Add (or remove) weight, saturating at the boundaries of 100 and 900. - pub fn thicken(self, delta: i16) -> Self { - Self((self.0 as i16).saturating_add(delta).max(100).min(900) as u16) - } - - /// The absolute number distance between this and another font weight. - pub fn distance(self, other: Self) -> u16 { - (self.0 as i16 - other.0 as i16).unsigned_abs() - } -} - -impl Default for FontWeight { - fn default() -> Self { - Self::REGULAR - } -} - -impl Debug for FontWeight { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -cast! { - FontWeight, - self => IntoValue::into_value(match self { - FontWeight::THIN => "thin", - FontWeight::EXTRALIGHT => "extralight", - FontWeight::LIGHT => "light", - FontWeight::REGULAR => "regular", - FontWeight::MEDIUM => "medium", - FontWeight::SEMIBOLD => "semibold", - FontWeight::BOLD => "bold", - FontWeight::EXTRABOLD => "extrabold", - FontWeight::BLACK => "black", - _ => return self.to_number().into_value(), - }), - v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16), - /// Thin weight (100). - "thin" => Self::THIN, - /// Extra light weight (200). - "extralight" => Self::EXTRALIGHT, - /// Light weight (300). - "light" => Self::LIGHT, - /// Regular weight (400). - "regular" => Self::REGULAR, - /// Medium weight (500). - "medium" => Self::MEDIUM, - /// Semibold weight (600). - "semibold" => Self::SEMIBOLD, - /// Bold weight (700). - "bold" => Self::BOLD, - /// Extrabold weight (800). - "extrabold" => Self::EXTRABOLD, - /// Black weight (900). - "black" => Self::BLACK, -} - -/// The width of a font. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize)] -#[serde(transparent)] -pub struct FontStretch(u16); - -impl FontStretch { - /// Ultra-condensed stretch (50%). - pub const ULTRA_CONDENSED: Self = Self(500); - - /// Extra-condensed stretch weight (62.5%). - pub const EXTRA_CONDENSED: Self = Self(625); - - /// Condensed stretch (75%). - pub const CONDENSED: Self = Self(750); - - /// Semi-condensed stretch (87.5%). - pub const SEMI_CONDENSED: Self = Self(875); - - /// Normal stretch (100%). - pub const NORMAL: Self = Self(1000); - - /// Semi-expanded stretch (112.5%). - pub const SEMI_EXPANDED: Self = Self(1125); - - /// Expanded stretch (125%). - pub const EXPANDED: Self = Self(1250); - - /// Extra-expanded stretch (150%). - pub const EXTRA_EXPANDED: Self = Self(1500); - - /// Ultra-expanded stretch (200%). - pub const ULTRA_EXPANDED: Self = Self(2000); - - /// Create a font stretch from a ratio between 0.5 and 2.0, clamping it if - /// necessary. - pub fn from_ratio(ratio: Ratio) -> Self { - Self((ratio.get().max(0.5).min(2.0) * 1000.0) as u16) - } - - /// Create a font stretch from an OpenType-style number between 1 and 9, - /// clamping it if necessary. - pub fn from_number(stretch: u16) -> Self { - match stretch { - 0 | 1 => Self::ULTRA_CONDENSED, - 2 => Self::EXTRA_CONDENSED, - 3 => Self::CONDENSED, - 4 => Self::SEMI_CONDENSED, - 5 => Self::NORMAL, - 6 => Self::SEMI_EXPANDED, - 7 => Self::EXPANDED, - 8 => Self::EXTRA_EXPANDED, - _ => Self::ULTRA_EXPANDED, - } - } - - /// The ratio between 0.5 and 2.0 corresponding to this stretch. - pub fn to_ratio(self) -> Ratio { - Ratio::new(self.0 as f64 / 1000.0) - } - - /// The absolute ratio distance between this and another font stretch. - pub fn distance(self, other: Self) -> Ratio { - (self.to_ratio() - other.to_ratio()).abs() - } -} - -impl Default for FontStretch { - fn default() -> Self { - Self::NORMAL - } -} - -impl Debug for FontStretch { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.to_ratio().fmt(f) - } -} - -cast! { - FontStretch, - self => self.to_ratio().into_value(), - v: Ratio => Self::from_ratio(v), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_font_weight_distance() { - let d = |a, b| FontWeight(a).distance(FontWeight(b)); - assert_eq!(d(500, 200), 300); - assert_eq!(d(500, 500), 0); - assert_eq!(d(500, 900), 400); - assert_eq!(d(10, 100), 90); - } - - #[test] - fn test_font_stretch_debug() { - assert_eq!(format!("{:?}", FontStretch::EXPANDED), "125%") - } -} diff --git a/src/geom/abs.rs b/src/geom/abs.rs deleted file mode 100644 index 4ca3a9a1..00000000 --- a/src/geom/abs.rs +++ /dev/null @@ -1,266 +0,0 @@ -use super::*; - -/// An absolute length. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Abs(Scalar); - -impl Abs { - /// The zero length. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// The infinite length. - pub const fn inf() -> Self { - Self(Scalar(f64::INFINITY)) - } - - /// Create an absolute length from a number of raw units. - pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) - } - - /// Create an absolute length from a value in a unit. - pub fn with_unit(val: f64, unit: AbsUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) - } - - /// Create an absolute length from a number of points. - pub fn pt(pt: f64) -> Self { - Self::with_unit(pt, AbsUnit::Pt) - } - - /// Create an absolute length from a number of millimeters. - pub fn mm(mm: f64) -> Self { - Self::with_unit(mm, AbsUnit::Mm) - } - - /// Create an absolute length from a number of centimeters. - pub fn cm(cm: f64) -> Self { - Self::with_unit(cm, AbsUnit::Cm) - } - - /// Create an absolute length from a number of inches. - pub fn inches(inches: f64) -> Self { - Self::with_unit(inches, AbsUnit::In) - } - - /// Get the value of this absolute length in raw units. - pub const fn to_raw(self) -> f64 { - (self.0).0 - } - - /// Get the value of this absolute length in a unit. - pub fn to_unit(self, unit: AbsUnit) -> f64 { - self.to_raw() / unit.raw_scale() - } - - /// Convert this to a number of points. - pub fn to_pt(self) -> f64 { - self.to_unit(AbsUnit::Pt) - } - - /// Convert this to a number of millimeters. - pub fn to_mm(self) -> f64 { - self.to_unit(AbsUnit::Mm) - } - - /// Convert this to a number of centimeters. - pub fn to_cm(self) -> f64 { - self.to_unit(AbsUnit::Cm) - } - - /// Convert this to a number of inches. - pub fn to_inches(self) -> f64 { - self.to_unit(AbsUnit::In) - } - - /// The absolute value of this length. - pub fn abs(self) -> Self { - Self::raw(self.to_raw().abs()) - } - - /// The minimum of this and another absolute length. - pub fn min(self, other: Self) -> Self { - Self(self.0.min(other.0)) - } - - /// Set to the minimum of this and another absolute length. - pub fn set_min(&mut self, other: Self) { - *self = (*self).min(other); - } - - /// The maximum of this and another absolute length. - pub fn max(self, other: Self) -> Self { - Self(self.0.max(other.0)) - } - - /// Set to the maximum of this and another absolute length. - pub fn set_max(&mut self, other: Self) { - *self = (*self).max(other); - } - - /// Whether the other absolute length fits into this one (i.e. is smaller). - /// Allows for a bit of slack. - pub fn fits(self, other: Self) -> bool { - self.0 + 1e-6 >= other.0 - } - - /// Compares two absolute lengths for whether they are approximately equal. - pub fn approx_eq(self, other: Self) -> bool { - self == other || (self - other).to_raw().abs() < 1e-6 - } - - /// Perform a checked division by a number, returning zero if the result - /// is not finite. - pub fn safe_div(self, number: f64) -> Self { - let result = self.to_raw() / number; - if result.is_finite() { - Self::raw(result) - } else { - Self::zero() - } - } -} - -impl Numeric for Abs { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl Debug for Abs { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}pt", round_2(self.to_pt())) - } -} - -impl Neg for Abs { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Abs { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Abs - Abs -> Abs); - -impl Mul<f64> for Abs { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Abs> for f64 { - type Output = Abs; - - fn mul(self, other: Abs) -> Abs { - other * self - } -} - -impl Div<f64> for Abs { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Abs { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.to_raw() / other.to_raw() - } -} - -assign_impl!(Abs += Abs); -assign_impl!(Abs -= Abs); -assign_impl!(Abs *= f64); -assign_impl!(Abs /= f64); - -impl Rem for Abs { - type Output = Self; - - fn rem(self, other: Self) -> Self::Output { - Self(self.0 % other.0) - } -} - -impl Sum for Abs { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -impl<'a> Sum<&'a Self> for Abs { - fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -cast! { - Abs, - self => Value::Length(self.into()), -} - -/// Different units of absolute measurement. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum AbsUnit { - /// Points. - Pt, - /// Millimeters. - Mm, - /// Centimeters. - Cm, - /// Inches. - In, -} - -impl AbsUnit { - /// How many raw units correspond to a value of `1.0` in this unit. - fn raw_scale(self) -> f64 { - match self { - AbsUnit::Pt => 1.0, - AbsUnit::Mm => 2.83465, - AbsUnit::Cm => 28.3465, - AbsUnit::In => 72.0, - } - } -} - -impl Debug for AbsUnit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - AbsUnit::Mm => "mm", - AbsUnit::Pt => "pt", - AbsUnit::Cm => "cm", - AbsUnit::In => "in", - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_length_unit_conversion() { - assert!((Abs::mm(150.0).to_cm() - 15.0) < 1e-4); - } -} diff --git a/src/geom/align.rs b/src/geom/align.rs deleted file mode 100644 index 47acd3a6..00000000 --- a/src/geom/align.rs +++ /dev/null @@ -1,239 +0,0 @@ -use super::*; - -/// Where to align something along an axis. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum Align { - /// Align at the left side. - Left, - /// Align in the horizontal middle. - Center, - /// Align at the right side. - Right, - /// Align at the top side. - Top, - /// Align in the vertical middle. - Horizon, - /// Align at the bottom side. - Bottom, -} - -impl Align { - /// Top-left alignment. - pub const LEFT_TOP: Axes<Self> = Axes { x: Align::Left, y: Align::Top }; - - /// Center-horizon alignment. - pub const CENTER_HORIZON: Axes<Self> = Axes { x: Align::Center, y: Align::Horizon }; - - /// The axis this alignment belongs to. - pub const fn axis(self) -> Axis { - match self { - Self::Left | Self::Center | Self::Right => Axis::X, - Self::Top | Self::Horizon | Self::Bottom => Axis::Y, - } - } - - /// The inverse alignment. - pub const fn inv(self) -> Self { - match self { - Self::Left => Self::Right, - Self::Center => Self::Center, - Self::Right => Self::Left, - Self::Top => Self::Bottom, - Self::Horizon => Self::Horizon, - Self::Bottom => Self::Top, - } - } - - /// Returns the position of this alignment in a container with the given - /// extent. - pub fn position(self, extent: Abs) -> Abs { - match self { - Self::Left | Self::Top => Abs::zero(), - Self::Center | Self::Horizon => extent / 2.0, - Self::Right | Self::Bottom => extent, - } - } -} - -impl From<Side> for Align { - fn from(side: Side) -> Self { - match side { - Side::Left => Self::Left, - Side::Top => Self::Top, - Side::Right => Self::Right, - Side::Bottom => Self::Bottom, - } - } -} - -impl Debug for Align { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Left => "left", - Self::Center => "center", - Self::Right => "right", - Self::Top => "top", - Self::Horizon => "horizon", - Self::Bottom => "bottom", - }) - } -} - -/// The generic alignment representation. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum GenAlign { - /// Align at the start side of the text direction. - Start, - /// Align at the end side of the text direction. - End, - /// Align at a specific alignment. - Specific(Align), -} - -impl GenAlign { - /// The axis this alignment belongs to. - pub const fn axis(self) -> Axis { - match self { - Self::Start | Self::End => Axis::X, - Self::Specific(align) => align.axis(), - } - } -} - -impl From<Align> for GenAlign { - fn from(align: Align) -> Self { - Self::Specific(align) - } -} - -impl From<HorizontalAlign> for GenAlign { - fn from(align: HorizontalAlign) -> Self { - align.0 - } -} - -impl From<VerticalAlign> for GenAlign { - fn from(align: VerticalAlign) -> Self { - align.0 - } -} - -impl Debug for GenAlign { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Start => f.pad("start"), - Self::End => f.pad("end"), - Self::Specific(align) => align.fmt(f), - } - } -} - -cast! { - type GenAlign: "alignment", -} - -cast! { - type Axes<GenAlign>: "2d alignment", -} - -cast! { - Axes<Align>, - self => self.map(GenAlign::from).into_value(), -} - -cast! { - Axes<Option<GenAlign>>, - self => match (self.x, self.y) { - (Some(x), Some(y)) => Axes::new(x, y).into_value(), - (Some(x), None) => x.into_value(), - (None, Some(y)) => y.into_value(), - (None, None) => Value::None, - }, - align: GenAlign => { - let mut aligns = Axes::default(); - aligns.set(align.axis(), Some(align)); - aligns - }, - aligns: Axes<GenAlign> => aligns.map(Some), -} - -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; - - fn resolve(self, styles: StyleChain) -> Self::Output { - let dir = item!(dir)(styles); - match self { - Self::Start => dir.start().into(), - Self::End => dir.end().into(), - Self::Specific(align) => align, - } - } -} - -impl Fold for GenAlign { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -impl Fold for Align { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -/// Utility struct to restrict a passed alignment value to the horizontal axis -/// on cast. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct HorizontalAlign(pub GenAlign); - -cast! { - HorizontalAlign, - self => self.0.into_value(), - align: GenAlign => { - if align.axis() != Axis::X { - bail!("alignment must be horizontal"); - } - Self(align) - }, -} - -/// Utility struct to restrict a passed alignment value to the vertical axis on -/// cast. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct VerticalAlign(pub GenAlign); - -cast! { - VerticalAlign, - self => self.0.into_value(), - align: GenAlign => { - if align.axis() != Axis::Y { - bail!("alignment must be vertical"); - } - Self(align) - }, -} diff --git a/src/geom/angle.rs b/src/geom/angle.rs deleted file mode 100644 index c03810d9..00000000 --- a/src/geom/angle.rs +++ /dev/null @@ -1,188 +0,0 @@ -use super::*; - -/// An angle. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Angle(Scalar); - -impl Angle { - /// The zero angle. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// Create an angle from a number of raw units. - pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) - } - - /// Create an angle from a value in a unit. - pub fn with_unit(val: f64, unit: AngleUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) - } - - /// Create an angle from a number of radians. - pub fn rad(rad: f64) -> Self { - Self::with_unit(rad, AngleUnit::Rad) - } - - /// Create an angle from a number of degrees. - pub fn deg(deg: f64) -> Self { - Self::with_unit(deg, AngleUnit::Deg) - } - - /// Get the value of this angle in raw units. - pub const fn to_raw(self) -> f64 { - (self.0).0 - } - - /// Get the value of this angle in a unit. - pub fn to_unit(self, unit: AngleUnit) -> f64 { - self.to_raw() / unit.raw_scale() - } - - /// Convert this to a number of radians. - pub fn to_rad(self) -> f64 { - self.to_unit(AngleUnit::Rad) - } - - /// Convert this to a number of degrees. - pub fn to_deg(self) -> f64 { - self.to_unit(AngleUnit::Deg) - } - - /// The absolute value of the this angle. - pub fn abs(self) -> Self { - Self::raw(self.to_raw().abs()) - } - - /// Get the sine of this angle in radians. - pub fn sin(self) -> f64 { - self.to_rad().sin() - } - - /// Get the cosine of this angle in radians. - pub fn cos(self) -> f64 { - self.to_rad().cos() - } - - /// Get the tangent of this angle in radians. - pub fn tan(self) -> f64 { - self.to_rad().tan() - } -} - -impl Numeric for Angle { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl Debug for Angle { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}deg", round_2(self.to_deg())) - } -} - -impl Neg for Angle { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Angle { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Angle - Angle -> Angle); - -impl Mul<f64> for Angle { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Angle> for f64 { - type Output = Angle; - - fn mul(self, other: Angle) -> Angle { - other * self - } -} - -impl Div<f64> for Angle { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Angle { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.to_raw() / other.to_raw() - } -} - -assign_impl!(Angle += Angle); -assign_impl!(Angle -= Angle); -assign_impl!(Angle *= f64); -assign_impl!(Angle /= f64); - -impl Sum for Angle { - fn sum<I: Iterator<Item = Angle>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -/// Different units of angular measurement. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum AngleUnit { - /// Radians. - Rad, - /// Degrees. - Deg, -} - -impl AngleUnit { - /// How many raw units correspond to a value of `1.0` in this unit. - fn raw_scale(self) -> f64 { - match self { - Self::Rad => 1.0, - Self::Deg => PI / 180.0, - } - } -} - -impl Debug for AngleUnit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Rad => "rad", - Self::Deg => "deg", - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_angle_unit_conversion() { - assert!((Angle::rad(2.0 * PI).to_deg() - 360.0) < 1e-4); - assert!((Angle::deg(45.0).to_rad() - std::f64::consts::FRAC_PI_4) < 1e-4); - } -} diff --git a/src/geom/axes.rs b/src/geom/axes.rs deleted file mode 100644 index 059d3bb2..00000000 --- a/src/geom/axes.rs +++ /dev/null @@ -1,305 +0,0 @@ -use std::any::Any; -use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; - -use super::*; - -/// A container with a horizontal and vertical component. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Axes<T> { - /// The horizontal component. - pub x: T, - /// The vertical component. - pub y: T, -} - -impl<T> Axes<T> { - /// Create a new instance from the two components. - pub const fn new(x: T, y: T) -> Self { - Self { x, y } - } - - /// Create a new instance with two equal components. - pub fn splat(v: T) -> Self - where - T: Clone, - { - Self { x: v.clone(), y: v } - } - - /// Map the individual fields with `f`. - pub fn map<F, U>(self, mut f: F) -> Axes<U> - where - F: FnMut(T) -> U, - { - Axes { x: f(self.x), y: f(self.y) } - } - - /// Convert from `&Axes<T>` to `Axes<&T>`. - pub fn as_ref(&self) -> Axes<&T> { - Axes { x: &self.x, y: &self.y } - } - - /// Convert from `&Axes<T>` to `Axes<&<T as Deref>::Target>`. - pub fn as_deref(&self) -> Axes<&T::Target> - where - T: Deref, - { - Axes { x: &self.x, y: &self.y } - } - - /// Convert from `&mut Axes<T>` to `Axes<&mut T>`. - pub fn as_mut(&mut self) -> Axes<&mut T> { - Axes { x: &mut self.x, y: &mut self.y } - } - - /// Zip two instances into an instance over a tuple. - pub fn zip<U>(self, other: Axes<U>) -> Axes<(T, U)> { - Axes { x: (self.x, other.x), y: (self.y, other.y) } - } - - /// Whether a condition is true for at least one of fields. - pub fn any<F>(self, mut f: F) -> bool - where - F: FnMut(&T) -> bool, - { - f(&self.x) || f(&self.y) - } - - /// Whether a condition is true for both fields. - pub fn all<F>(self, mut f: F) -> bool - where - F: FnMut(&T) -> bool, - { - f(&self.x) && f(&self.y) - } - - /// Filter the individual fields with a mask. - pub fn filter(self, mask: Axes<bool>) -> Axes<Option<T>> { - Axes { - x: if mask.x { Some(self.x) } else { None }, - y: if mask.y { Some(self.y) } else { None }, - } - } -} - -impl<T: Default> Axes<T> { - /// Create a new instance with y set to its default value. - pub fn with_x(x: T) -> Self { - Self { x, y: T::default() } - } - - /// Create a new instance with x set to its default value. - pub fn with_y(y: T) -> Self { - Self { x: T::default(), y } - } -} - -impl<T: Ord> Axes<T> { - /// The component-wise minimum of this and another instance. - pub fn min(self, other: Self) -> Self { - Self { x: self.x.min(other.x), y: self.y.min(other.y) } - } - - /// The component-wise minimum of this and another instance. - pub fn max(self, other: Self) -> Self { - Self { x: self.x.max(other.x), y: self.y.max(other.y) } - } - - /// The minimum of width and height. - pub fn min_by_side(self) -> T { - self.x.min(self.y) - } - - /// The minimum of width and height. - pub fn max_by_side(self) -> T { - self.x.max(self.y) - } -} - -impl<T> Get<Axis> for Axes<T> { - type Component = T; - - fn get_ref(&self, axis: Axis) -> &T { - match axis { - Axis::X => &self.x, - Axis::Y => &self.y, - } - } - - fn get_mut(&mut self, axis: Axis) -> &mut T { - match axis { - Axis::X => &mut self.x, - Axis::Y => &mut self.y, - } - } -} - -impl<T> Debug for Axes<T> -where - T: Debug + 'static, -{ - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Axes { x: Some(x), y: Some(y) } = - self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<GenAlign>()) - { - write!(f, "{:?} + {:?}", x, y) - } else if (&self.x as &dyn Any).is::<Abs>() { - write!(f, "Size({:?}, {:?})", self.x, self.y) - } else { - write!(f, "Axes({:?}, {:?})", self.x, self.y) - } - } -} - -/// The two layouting axes. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum Axis { - /// The horizontal axis. - X, - /// The vertical axis. - Y, -} - -impl Axis { - /// The direction with the given positivity for this axis. - pub fn dir(self, positive: bool) -> Dir { - match (self, positive) { - (Self::X, true) => Dir::LTR, - (Self::X, false) => Dir::RTL, - (Self::Y, true) => Dir::TTB, - (Self::Y, false) => Dir::BTT, - } - } - - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::X => Self::Y, - Self::Y => Self::X, - } - } -} - -impl Debug for Axis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::X => "horizontal", - Self::Y => "vertical", - }) - } -} - -impl<T> Axes<Option<T>> { - /// Unwrap the individual fields. - pub fn unwrap_or(self, other: Axes<T>) -> Axes<T> { - Axes { - x: self.x.unwrap_or(other.x), - y: self.y.unwrap_or(other.y), - } - } -} - -impl<T> Axes<Smart<T>> { - /// Unwrap the individual fields. - pub fn unwrap_or(self, other: Axes<T>) -> Axes<T> { - Axes { - x: self.x.unwrap_or(other.x), - y: self.y.unwrap_or(other.y), - } - } -} - -impl Axes<bool> { - /// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`. - pub fn select<T>(self, t: Axes<T>, f: Axes<T>) -> Axes<T> { - Axes { - x: if self.x { t.x } else { f.x }, - y: if self.y { t.y } else { f.y }, - } - } -} - -impl Not for Axes<bool> { - type Output = Self; - - fn not(self) -> Self::Output { - Self { x: !self.x, y: !self.y } - } -} - -impl BitOr for Axes<bool> { - type Output = Self; - - fn bitor(self, rhs: Self) -> Self::Output { - Self { x: self.x | rhs.x, y: self.y | rhs.y } - } -} - -impl BitOr<bool> for Axes<bool> { - type Output = Self; - - fn bitor(self, rhs: bool) -> Self::Output { - Self { x: self.x | rhs, y: self.y | rhs } - } -} - -impl BitAnd for Axes<bool> { - type Output = Self; - - fn bitand(self, rhs: Self) -> Self::Output { - Self { x: self.x & rhs.x, y: self.y & rhs.y } - } -} - -impl BitAnd<bool> for Axes<bool> { - type Output = Self; - - fn bitand(self, rhs: bool) -> Self::Output { - Self { x: self.x & rhs, y: self.y & rhs } - } -} - -impl BitOrAssign for Axes<bool> { - fn bitor_assign(&mut self, rhs: Self) { - self.x |= rhs.x; - self.y |= rhs.y; - } -} - -impl BitAndAssign for Axes<bool> { - fn bitand_assign(&mut self, rhs: Self) { - self.x &= rhs.x; - self.y &= rhs.y; - } -} - -cast! { - Axes<Rel<Length>>, - self => array![self.x, self.y].into_value(), - array: Array => { - let mut iter = array.into_iter(); - match (iter.next(), iter.next(), iter.next()) { - (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), - _ => bail!("point array must contain exactly two entries"), - } - }, -} - -impl<T: Resolve> Resolve for Axes<T> { - type Output = Axes<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T: Fold> Fold for Axes<Option<T>> { - type Output = Axes<T::Output>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer).map(|(inner, outer)| match inner { - Some(value) => value.fold(outer), - None => outer, - }) - } -} diff --git a/src/geom/color.rs b/src/geom/color.rs deleted file mode 100644 index c7676c2b..00000000 --- a/src/geom/color.rs +++ /dev/null @@ -1,386 +0,0 @@ -use std::str::FromStr; - -use super::*; - -/// A color in a dynamic format. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum Color { - /// An 8-bit luma color. - Luma(LumaColor), - /// An 8-bit RGBA color. - Rgba(RgbaColor), - /// An 8-bit CMYK color. - Cmyk(CmykColor), -} - -impl Color { - pub const BLACK: Self = Self::Rgba(RgbaColor::new(0x00, 0x00, 0x00, 0xFF)); - pub const GRAY: Self = Self::Rgba(RgbaColor::new(0xAA, 0xAA, 0xAA, 0xFF)); - pub const SILVER: Self = Self::Rgba(RgbaColor::new(0xDD, 0xDD, 0xDD, 0xFF)); - pub const WHITE: Self = Self::Rgba(RgbaColor::new(0xFF, 0xFF, 0xFF, 0xFF)); - pub const NAVY: Self = Self::Rgba(RgbaColor::new(0x00, 0x1f, 0x3f, 0xFF)); - pub const BLUE: Self = Self::Rgba(RgbaColor::new(0x00, 0x74, 0xD9, 0xFF)); - pub const AQUA: Self = Self::Rgba(RgbaColor::new(0x7F, 0xDB, 0xFF, 0xFF)); - pub const TEAL: Self = Self::Rgba(RgbaColor::new(0x39, 0xCC, 0xCC, 0xFF)); - pub const EASTERN: Self = Self::Rgba(RgbaColor::new(0x23, 0x9D, 0xAD, 0xFF)); - pub const PURPLE: Self = Self::Rgba(RgbaColor::new(0xB1, 0x0D, 0xC9, 0xFF)); - pub const FUCHSIA: Self = Self::Rgba(RgbaColor::new(0xF0, 0x12, 0xBE, 0xFF)); - pub const MAROON: Self = Self::Rgba(RgbaColor::new(0x85, 0x14, 0x4b, 0xFF)); - pub const RED: Self = Self::Rgba(RgbaColor::new(0xFF, 0x41, 0x36, 0xFF)); - pub const ORANGE: Self = Self::Rgba(RgbaColor::new(0xFF, 0x85, 0x1B, 0xFF)); - pub const YELLOW: Self = Self::Rgba(RgbaColor::new(0xFF, 0xDC, 0x00, 0xFF)); - pub const OLIVE: Self = Self::Rgba(RgbaColor::new(0x3D, 0x99, 0x70, 0xFF)); - pub const GREEN: Self = Self::Rgba(RgbaColor::new(0x2E, 0xCC, 0x40, 0xFF)); - pub const LIME: Self = Self::Rgba(RgbaColor::new(0x01, 0xFF, 0x70, 0xFF)); - - /// Convert this color to RGBA. - pub fn to_rgba(self) -> RgbaColor { - match self { - Self::Luma(luma) => luma.to_rgba(), - Self::Rgba(rgba) => rgba, - Self::Cmyk(cmyk) => cmyk.to_rgba(), - } - } - - /// Lighten this color by the given factor. - pub fn lighten(self, factor: Ratio) -> Self { - match self { - Self::Luma(luma) => Self::Luma(luma.lighten(factor)), - Self::Rgba(rgba) => Self::Rgba(rgba.lighten(factor)), - Self::Cmyk(cmyk) => Self::Cmyk(cmyk.lighten(factor)), - } - } - - /// Darken this color by the given factor. - pub fn darken(self, factor: Ratio) -> Self { - match self { - Self::Luma(luma) => Self::Luma(luma.darken(factor)), - Self::Rgba(rgba) => Self::Rgba(rgba.darken(factor)), - Self::Cmyk(cmyk) => Self::Cmyk(cmyk.darken(factor)), - } - } - - /// Negate this color. - pub fn negate(self) -> Self { - match self { - Self::Luma(luma) => Self::Luma(luma.negate()), - Self::Rgba(rgba) => Self::Rgba(rgba.negate()), - Self::Cmyk(cmyk) => Self::Cmyk(cmyk.negate()), - } - } -} - -impl Debug for Color { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Luma(c) => Debug::fmt(c, f), - Self::Rgba(c) => Debug::fmt(c, f), - Self::Cmyk(c) => Debug::fmt(c, f), - } - } -} - -/// An 8-bit grayscale color. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct LumaColor(pub u8); - -impl LumaColor { - /// Construct a new luma color. - pub const fn new(luma: u8) -> Self { - Self(luma) - } - - /// Convert to an opque RGBA color. - pub const fn to_rgba(self) -> RgbaColor { - RgbaColor::new(self.0, self.0, self.0, u8::MAX) - } - - /// Convert to CMYK as a fraction of true black. - pub fn to_cmyk(self) -> CmykColor { - CmykColor::new( - round_u8(self.0 as f64 * 0.75), - round_u8(self.0 as f64 * 0.68), - round_u8(self.0 as f64 * 0.67), - round_u8(self.0 as f64 * 0.90), - ) - } - - /// Lighten this color by a factor. - pub fn lighten(self, factor: Ratio) -> Self { - let inc = round_u8((u8::MAX - self.0) as f64 * factor.get()); - Self(self.0.saturating_add(inc)) - } - - /// Darken this color by a factor. - pub fn darken(self, factor: Ratio) -> Self { - let dec = round_u8(self.0 as f64 * factor.get()); - Self(self.0.saturating_sub(dec)) - } - - /// Negate this color. - pub fn negate(self) -> Self { - Self(u8::MAX - self.0) - } -} - -impl Debug for LumaColor { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "luma({})", self.0) - } -} - -impl From<LumaColor> for Color { - fn from(luma: LumaColor) -> Self { - Self::Luma(luma) - } -} - -/// An 8-bit RGBA color. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct RgbaColor { - /// Red channel. - pub r: u8, - /// Green channel. - pub g: u8, - /// Blue channel. - pub b: u8, - /// Alpha channel. - pub a: u8, -} - -impl RgbaColor { - /// Construct a new RGBA color. - pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a } - } - - /// Lighten this color by a factor. - /// - /// The alpha channel is not affected. - pub fn lighten(self, factor: Ratio) -> Self { - let lighten = - |c: u8| c.saturating_add(round_u8((u8::MAX - c) as f64 * factor.get())); - Self { - r: lighten(self.r), - g: lighten(self.g), - b: lighten(self.b), - a: self.a, - } - } - - /// Darken this color by a factor. - /// - /// The alpha channel is not affected. - pub fn darken(self, factor: Ratio) -> Self { - let darken = |c: u8| c.saturating_sub(round_u8(c as f64 * factor.get())); - Self { - r: darken(self.r), - g: darken(self.g), - b: darken(self.b), - a: self.a, - } - } - - /// Negate this color. - /// - /// The alpha channel is not affected. - pub fn negate(self) -> Self { - Self { - r: u8::MAX - self.r, - g: u8::MAX - self.g, - b: u8::MAX - self.b, - a: self.a, - } - } -} - -impl FromStr for RgbaColor { - type Err = &'static str; - - /// Constructs a new color from hex strings like the following: - /// - `#aef` (shorthand, with leading hashtag), - /// - `7a03c2` (without alpha), - /// - `abcdefff` (with alpha). - /// - /// The hashtag is optional and both lower and upper case are fine. - fn from_str(hex_str: &str) -> Result<Self, Self::Err> { - let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str); - if hex_str.chars().any(|c| !c.is_ascii_hexdigit()) { - return Err("color string contains non-hexadecimal letters"); - } - - let len = hex_str.len(); - let long = len == 6 || len == 8; - let short = len == 3 || len == 4; - let alpha = len == 4 || len == 8; - if !long && !short { - return Err("color string has wrong length"); - } - - let mut values: [u8; 4] = [u8::MAX; 4]; - for elem in if alpha { 0..4 } else { 0..3 } { - let item_len = if long { 2 } else { 1 }; - let pos = elem * item_len; - - let item = &hex_str[pos..(pos + item_len)]; - values[elem] = u8::from_str_radix(item, 16).unwrap(); - - if short { - // Duplicate number for shorthand notation, i.e. `a` -> `aa` - values[elem] += values[elem] * 16; - } - } - - Ok(Self::new(values[0], values[1], values[2], values[3])) - } -} - -impl Debug for RgbaColor { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - write!(f, "rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a,)?; - } else { - write!(f, "rgb(\"#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?; - if self.a != 255 { - write!(f, "{:02x}", self.a)?; - } - write!(f, "\")")?; - } - Ok(()) - } -} - -impl<T: Into<RgbaColor>> From<T> for Color { - fn from(rgba: T) -> Self { - Self::Rgba(rgba.into()) - } -} - -cast! { - RgbaColor, - self => Value::Color(self.into()), -} - -/// An 8-bit CMYK color. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct CmykColor { - /// The cyan component. - pub c: u8, - /// The magenta component. - pub m: u8, - /// The yellow component. - pub y: u8, - /// The key (black) component. - pub k: u8, -} - -impl CmykColor { - /// Construct a new CMYK color. - pub const fn new(c: u8, m: u8, y: u8, k: u8) -> Self { - Self { c, m, y, k } - } - - /// Convert this color to RGBA. - pub fn to_rgba(self) -> RgbaColor { - let k = self.k as f64 / 255.0; - let f = |c| { - let c = c as f64 / 255.0; - round_u8(255.0 * (1.0 - c) * (1.0 - k)) - }; - - RgbaColor { r: f(self.c), g: f(self.m), b: f(self.y), a: 255 } - } - - /// Lighten this color by a factor. - pub fn lighten(self, factor: Ratio) -> Self { - let lighten = |c: u8| c.saturating_sub(round_u8(c as f64 * factor.get())); - Self { - c: lighten(self.c), - m: lighten(self.m), - y: lighten(self.y), - k: lighten(self.k), - } - } - - /// Darken this color by a factor. - pub fn darken(self, factor: Ratio) -> Self { - let darken = - |c: u8| c.saturating_add(round_u8((u8::MAX - c) as f64 * factor.get())); - Self { - c: darken(self.c), - m: darken(self.m), - y: darken(self.y), - k: darken(self.k), - } - } - - /// Negate this color. - /// - /// Does not affect the key component. - pub fn negate(self) -> Self { - Self { - c: u8::MAX - self.c, - m: u8::MAX - self.m, - y: u8::MAX - self.y, - k: self.k, - } - } -} - -impl Debug for CmykColor { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let g = |c| 100.0 * (c as f64 / 255.0); - write!( - f, - "cmyk({:.1}%, {:.1}%, {:.1}%, {:.1}%)", - g(self.c), - g(self.m), - g(self.y), - g(self.k), - ) - } -} - -impl From<CmykColor> for Color { - fn from(cmyk: CmykColor) -> Self { - Self::Cmyk(cmyk) - } -} - -/// Convert to the closest u8. -fn round_u8(value: f64) -> u8 { - value.round() as u8 -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_color_strings() { - #[track_caller] - fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) { - assert_eq!(RgbaColor::from_str(hex), Ok(RgbaColor::new(r, g, b, a))); - } - - test("f61243ff", 0xf6, 0x12, 0x43, 0xff); - test("b3d8b3", 0xb3, 0xd8, 0xb3, 0xff); - test("fCd2a9AD", 0xfc, 0xd2, 0xa9, 0xad); - test("233", 0x22, 0x33, 0x33, 0xff); - test("111b", 0x11, 0x11, 0x11, 0xbb); - } - - #[test] - fn test_parse_invalid_colors() { - #[track_caller] - fn test(hex: &str, message: &str) { - assert_eq!(RgbaColor::from_str(hex), Err(message)); - } - - test("a5", "color string has wrong length"); - test("12345", "color string has wrong length"); - test("f075ff011", "color string has wrong length"); - test("hmmm", "color string contains non-hexadecimal letters"); - test("14B2AH", "color string contains non-hexadecimal letters"); - } -} diff --git a/src/geom/corners.rs b/src/geom/corners.rs deleted file mode 100644 index 5ee1e063..00000000 --- a/src/geom/corners.rs +++ /dev/null @@ -1,219 +0,0 @@ -use crate::eval::{CastInfo, FromValue, IntoValue, Reflect}; - -use super::*; - -/// A container with components for the four corners of a rectangle. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Corners<T> { - /// The value for the top left corner. - pub top_left: T, - /// The value for the top right corner. - pub top_right: T, - /// The value for the bottom right corner. - pub bottom_right: T, - /// The value for the bottom left corner. - pub bottom_left: T, -} - -impl<T> Corners<T> { - /// Create a new instance from the four components. - pub const fn new(top_left: T, top_right: T, bottom_right: T, bottom_left: T) -> Self { - Self { top_left, top_right, bottom_right, bottom_left } - } - - /// Create an instance with four equal components. - pub fn splat(value: T) -> Self - where - T: Clone, - { - Self { - top_left: value.clone(), - top_right: value.clone(), - bottom_right: value.clone(), - bottom_left: value, - } - } - - /// Map the individual fields with `f`. - pub fn map<F, U>(self, mut f: F) -> Corners<U> - where - F: FnMut(T) -> U, - { - Corners { - top_left: f(self.top_left), - top_right: f(self.top_right), - bottom_right: f(self.bottom_right), - bottom_left: f(self.bottom_left), - } - } - - /// Zip two instances into one. - pub fn zip<U>(self, other: Corners<U>) -> Corners<(T, U)> { - Corners { - top_left: (self.top_left, other.top_left), - top_right: (self.top_right, other.top_right), - bottom_right: (self.bottom_right, other.bottom_right), - bottom_left: (self.bottom_left, other.bottom_left), - } - } - - /// An iterator over the corners, starting with the top left corner, - /// clockwise. - pub fn iter(&self) -> impl Iterator<Item = &T> { - [&self.top_left, &self.top_right, &self.bottom_right, &self.bottom_left] - .into_iter() - } - - /// Whether all sides are equal. - pub fn is_uniform(&self) -> bool - where - T: PartialEq, - { - self.top_left == self.top_right - && self.top_right == self.bottom_right - && self.bottom_right == self.bottom_left - } -} - -impl<T> Get<Corner> for Corners<T> { - type Component = T; - - fn get_ref(&self, corner: Corner) -> &T { - match corner { - Corner::TopLeft => &self.top_left, - Corner::TopRight => &self.top_right, - Corner::BottomRight => &self.bottom_right, - Corner::BottomLeft => &self.bottom_left, - } - } - - fn get_mut(&mut self, corner: Corner) -> &mut T { - match corner { - Corner::TopLeft => &mut self.top_left, - Corner::TopRight => &mut self.top_right, - Corner::BottomRight => &mut self.bottom_right, - Corner::BottomLeft => &mut self.bottom_left, - } - } -} - -/// The four corners of a rectangle. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Corner { - /// The top left corner. - TopLeft, - /// The top right corner. - TopRight, - /// The bottom right corner. - BottomRight, - /// The bottom left corner. - BottomLeft, -} - -impl<T: Reflect> Reflect for Corners<Option<T>> { - fn describe() -> CastInfo { - T::describe() + Dict::describe() - } - - fn castable(value: &Value) -> bool { - Dict::castable(value) || T::castable(value) - } -} - -impl<T> IntoValue for Corners<T> -where - T: PartialEq + IntoValue, -{ - fn into_value(self) -> Value { - if self.is_uniform() { - return self.top_left.into_value(); - } - - let mut dict = Dict::new(); - let mut handle = |key: &str, component: T| { - let value = component.into_value(); - if value != Value::None { - dict.insert(key.into(), value); - } - }; - - handle("top-left", self.top_left); - handle("top-right", self.top_right); - handle("bottom-right", self.bottom_right); - handle("bottom-left", self.bottom_left); - - Value::Dict(dict) - } -} - -impl<T> FromValue for Corners<Option<T>> -where - T: FromValue + Clone, -{ - fn from_value(mut value: Value) -> StrResult<Self> { - let keys = [ - "top-left", - "top-right", - "bottom-right", - "bottom-left", - "left", - "top", - "right", - "bottom", - "rest", - ]; - - if let Value::Dict(dict) = &mut value { - if dict.iter().any(|(key, _)| keys.contains(&key.as_str())) { - let mut take = |key| dict.take(key).ok().map(T::from_value).transpose(); - let rest = take("rest")?; - let left = take("left")?.or_else(|| rest.clone()); - let top = take("top")?.or_else(|| rest.clone()); - let right = take("right")?.or_else(|| rest.clone()); - let bottom = take("bottom")?.or_else(|| rest.clone()); - let corners = Corners { - top_left: take("top-left")? - .or_else(|| top.clone()) - .or_else(|| left.clone()), - top_right: take("top-right")? - .or_else(|| top.clone()) - .or_else(|| right.clone()), - bottom_right: take("bottom-right")? - .or_else(|| bottom.clone()) - .or_else(|| right.clone()), - bottom_left: take("bottom-left")? - .or_else(|| bottom.clone()) - .or_else(|| left.clone()), - }; - - dict.finish(&keys)?; - return Ok(corners); - } - } - - if T::castable(&value) { - Ok(Self::splat(Some(T::from_value(value)?))) - } else { - Err(Self::error(&value)) - } - } -} - -impl<T: Resolve> Resolve for Corners<T> { - type Output = Corners<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T: Fold> Fold for Corners<Option<T>> { - type Output = Corners<T::Output>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer).map(|(inner, outer)| match inner { - Some(value) => value.fold(outer), - None => outer, - }) - } -} diff --git a/src/geom/dir.rs b/src/geom/dir.rs deleted file mode 100644 index 48915471..00000000 --- a/src/geom/dir.rs +++ /dev/null @@ -1,79 +0,0 @@ -use super::*; - -/// The four directions into which content can be laid out. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum Dir { - /// Left to right. - LTR, - /// Right to left. - RTL, - /// Top to bottom. - TTB, - /// Bottom to top. - BTT, -} - -impl Dir { - /// The specific axis this direction belongs to. - pub const fn axis(self) -> Axis { - match self { - Self::LTR | Self::RTL => Axis::X, - Self::TTB | Self::BTT => Axis::Y, - } - } - - /// The side this direction starts at. - pub const fn start(self) -> Side { - match self { - Self::LTR => Side::Left, - Self::RTL => Side::Right, - Self::TTB => Side::Top, - Self::BTT => Side::Bottom, - } - } - - /// The side this direction ends at. - pub const fn end(self) -> Side { - match self { - Self::LTR => Side::Right, - Self::RTL => Side::Left, - Self::TTB => Side::Bottom, - Self::BTT => Side::Top, - } - } - - /// The inverse direction. - pub const fn inv(self) -> Self { - match self { - Self::LTR => Self::RTL, - Self::RTL => Self::LTR, - Self::TTB => Self::BTT, - Self::BTT => Self::TTB, - } - } - - /// Whether this direction points into the positive coordinate direction. - /// - /// The positive directions are left-to-right and top-to-bottom. - pub const fn is_positive(self) -> bool { - match self { - Self::LTR | Self::TTB => true, - Self::RTL | Self::BTT => false, - } - } -} - -impl Debug for Dir { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::LTR => "ltr", - Self::RTL => "rtl", - Self::TTB => "ttb", - Self::BTT => "btt", - }) - } -} - -cast! { - type Dir: "direction", -} diff --git a/src/geom/ellipse.rs b/src/geom/ellipse.rs deleted file mode 100644 index ac20ffd3..00000000 --- a/src/geom/ellipse.rs +++ /dev/null @@ -1,22 +0,0 @@ -use super::*; - -/// Produce a shape that approximates an axis-aligned ellipse. -pub fn ellipse(size: Size, fill: Option<Paint>, stroke: Option<Stroke>) -> Shape { - // https://stackoverflow.com/a/2007782 - let z = Abs::zero(); - let rx = size.x / 2.0; - let ry = size.y / 2.0; - let m = 0.551784; - let mx = m * rx; - let my = m * ry; - let point = |x, y| Point::new(x + rx, y + ry); - - let mut path = Path::new(); - path.move_to(point(-rx, z)); - path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry)); - path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z)); - path.cubic_to(point(rx, my), point(mx, ry), point(z, ry)); - path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z)); - - Shape { geometry: Geometry::Path(path), stroke, fill } -} diff --git a/src/geom/em.rs b/src/geom/em.rs deleted file mode 100644 index 8dda9ff6..00000000 --- a/src/geom/em.rs +++ /dev/null @@ -1,153 +0,0 @@ -use super::*; - -/// A length that is relative to the font size. -/// -/// `1em` is the same as the font size. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Em(Scalar); - -impl Em { - /// The zero em length. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// The font size. - pub const fn one() -> Self { - Self(Scalar(1.0)) - } - - /// Create a font-relative length. - pub const fn new(em: f64) -> Self { - Self(Scalar(em)) - } - - /// Create an em length from font units at the given units per em. - pub fn from_units(units: impl Into<f64>, units_per_em: f64) -> Self { - Self(Scalar(units.into() / units_per_em)) - } - - /// Create an em length from a length at the given font size. - pub fn from_length(length: Abs, font_size: Abs) -> Self { - let result = length / font_size; - if result.is_finite() { - Self(Scalar(result)) - } else { - Self::zero() - } - } - - /// The number of em units. - pub const fn get(self) -> f64 { - (self.0).0 - } - - /// The absolute value of this em length. - pub fn abs(self) -> Self { - Self::new(self.get().abs()) - } - - /// Convert to an absolute length at the given font size. - pub fn at(self, font_size: Abs) -> Abs { - let resolved = font_size * self.get(); - if resolved.is_finite() { - resolved - } else { - Abs::zero() - } - } -} - -impl Numeric for Em { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl Debug for Em { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}em", self.get()) - } -} - -impl Neg for Em { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Em { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Em - Em -> Em); - -impl Mul<f64> for Em { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Em> for f64 { - type Output = Em; - - fn mul(self, other: Em) -> Em { - other * self - } -} - -impl Div<f64> for Em { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Em { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.get() / other.get() - } -} - -assign_impl!(Em += Em); -assign_impl!(Em -= Em); -assign_impl!(Em *= f64); -assign_impl!(Em /= f64); - -impl Sum for Em { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -cast! { - Em, - self => Value::Length(self.into()), -} - -impl Resolve for Em { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - if self.is_zero() { - Abs::zero() - } else { - self.at(item!(em)(styles)) - } - } -} diff --git a/src/geom/fr.rs b/src/geom/fr.rs deleted file mode 100644 index c602634d..00000000 --- a/src/geom/fr.rs +++ /dev/null @@ -1,119 +0,0 @@ -use super::*; - -/// A fraction of remaining space. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Fr(Scalar); - -impl Fr { - /// Takes up zero space: `0fr`. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// Takes up as much space as all other items with this fraction: `1fr`. - pub const fn one() -> Self { - Self(Scalar(1.0)) - } - - /// Create a new fraction. - pub const fn new(ratio: f64) -> Self { - Self(Scalar(ratio)) - } - - /// Get the underlying number. - pub const fn get(self) -> f64 { - (self.0).0 - } - - /// The absolute value of this fraction. - pub fn abs(self) -> Self { - Self::new(self.get().abs()) - } - - /// Determine this fraction's share in the remaining space. - pub fn share(self, total: Self, remaining: Abs) -> Abs { - let ratio = self / total; - if ratio.is_finite() && remaining.is_finite() { - (ratio * remaining).max(Abs::zero()) - } else { - Abs::zero() - } - } -} - -impl Numeric for Fr { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl Debug for Fr { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}fr", round_2(self.get())) - } -} - -impl Neg for Fr { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Fr { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Fr - Fr -> Fr); - -impl Mul<f64> for Fr { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Fr> for f64 { - type Output = Fr; - - fn mul(self, other: Fr) -> Fr { - other * self - } -} - -impl Div<f64> for Fr { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Fr { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.get() / other.get() - } -} - -assign_impl!(Fr += Fr); -assign_impl!(Fr -= Fr); -assign_impl!(Fr *= f64); -assign_impl!(Fr /= f64); - -impl Sum for Fr { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} diff --git a/src/geom/length.rs b/src/geom/length.rs deleted file mode 100644 index 7d0a9841..00000000 --- a/src/geom/length.rs +++ /dev/null @@ -1,128 +0,0 @@ -use super::*; - -/// A size or distance, possibly expressed with contextual units. -/// -/// Currently supports absolute and font-relative units, but support could quite -/// easily be extended to other units. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Length { - /// The absolute part. - pub abs: Abs, - /// The font-relative part. - pub em: Em, -} - -impl Length { - /// The zero length. - pub const fn zero() -> Self { - Self { abs: Abs::zero(), em: Em::zero() } - } - - /// Try to compute the absolute value of the length. - pub fn try_abs(self) -> Option<Self> { - (self.abs.is_zero() || self.em.is_zero()) - .then(|| Self { abs: self.abs.abs(), em: self.em.abs() }) - } - - /// Try to divide two lengths. - pub fn try_div(self, other: Self) -> Option<f64> { - if self.abs.is_zero() && other.abs.is_zero() { - Some(self.em / other.em) - } else if self.em.is_zero() && other.em.is_zero() { - Some(self.abs / other.abs) - } else { - None - } - } -} - -impl Debug for Length { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match (self.abs.is_zero(), self.em.is_zero()) { - (false, false) => write!(f, "{:?} + {:?}", self.abs, self.em), - (true, false) => self.em.fmt(f), - (_, true) => self.abs.fmt(f), - } - } -} - -impl Numeric for Length { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.abs.is_finite() && self.em.is_finite() - } -} - -impl PartialOrd for Length { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - if self.em.is_zero() && other.em.is_zero() { - self.abs.partial_cmp(&other.abs) - } else if self.abs.is_zero() && other.abs.is_zero() { - self.em.partial_cmp(&other.em) - } else { - None - } - } -} - -impl From<Abs> for Length { - fn from(abs: Abs) -> Self { - Self { abs, em: Em::zero() } - } -} - -impl From<Em> for Length { - fn from(em: Em) -> Self { - Self { abs: Abs::zero(), em } - } -} - -impl Neg for Length { - type Output = Self; - - fn neg(self) -> Self::Output { - Self { abs: -self.abs, em: -self.em } - } -} - -impl Add for Length { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self { abs: self.abs + rhs.abs, em: self.em + rhs.em } - } -} - -sub_impl!(Length - Length -> Length); - -impl Mul<f64> for Length { - type Output = Self; - - fn mul(self, rhs: f64) -> Self::Output { - Self { abs: self.abs * rhs, em: self.em * rhs } - } -} - -impl Div<f64> for Length { - type Output = Self; - - fn div(self, rhs: f64) -> Self::Output { - Self { abs: self.abs / rhs, em: self.em / rhs } - } -} - -assign_impl!(Length += Length); -assign_impl!(Length -= Length); -assign_impl!(Length *= f64); -assign_impl!(Length /= f64); - -impl Resolve for Length { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.abs + self.em.resolve(styles) - } -} diff --git a/src/geom/macros.rs b/src/geom/macros.rs deleted file mode 100644 index b1b50e22..00000000 --- a/src/geom/macros.rs +++ /dev/null @@ -1,47 +0,0 @@ -/// Implement the `Sub` trait based on existing `Neg` and `Add` impls. -macro_rules! sub_impl { - ($a:ident - $b:ident -> $c:ident) => { - impl std::ops::Sub<$b> for $a { - type Output = $c; - - fn sub(self, other: $b) -> $c { - self + -other - } - } - }; -} - -/// Implement an assign trait based on an existing non-assign trait. -macro_rules! assign_impl { - ($a:ident += $b:ident) => { - impl std::ops::AddAssign<$b> for $a { - fn add_assign(&mut self, other: $b) { - *self = *self + other; - } - } - }; - - ($a:ident -= $b:ident) => { - impl std::ops::SubAssign<$b> for $a { - fn sub_assign(&mut self, other: $b) { - *self = *self - other; - } - } - }; - - ($a:ident *= $b:ident) => { - impl std::ops::MulAssign<$b> for $a { - fn mul_assign(&mut self, other: $b) { - *self = *self * other; - } - } - }; - - ($a:ident /= $b:ident) => { - impl std::ops::DivAssign<$b> for $a { - fn div_assign(&mut self, other: $b) { - *self = *self / other; - } - } - }; -} diff --git a/src/geom/mod.rs b/src/geom/mod.rs deleted file mode 100644 index b7a7ff40..00000000 --- a/src/geom/mod.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! Geometrical primitives. - -#[macro_use] -mod macros; -mod abs; -mod align; -mod angle; -mod axes; -mod color; -mod corners; -mod dir; -mod ellipse; -mod em; -mod fr; -mod length; -mod paint; -mod path; -mod point; -mod ratio; -mod rel; -mod rounded; -mod scalar; -mod shape; -mod sides; -mod size; -mod smart; -mod stroke; -mod transform; - -pub use self::abs::{Abs, AbsUnit}; -pub use self::align::{Align, GenAlign, HorizontalAlign, VerticalAlign}; -pub use self::angle::{Angle, AngleUnit}; -pub use self::axes::{Axes, Axis}; -pub use self::color::{CmykColor, Color, LumaColor, RgbaColor}; -pub use self::corners::{Corner, Corners}; -pub use self::dir::Dir; -pub use self::ellipse::ellipse; -pub use self::em::Em; -pub use self::fr::Fr; -pub use self::length::Length; -pub use self::paint::Paint; -pub use self::path::{Path, PathItem}; -pub use self::point::Point; -pub use self::ratio::Ratio; -pub use self::rel::Rel; -pub use self::rounded::rounded_rect; -pub use self::scalar::Scalar; -pub use self::shape::{Geometry, Shape}; -pub use self::sides::{Side, Sides}; -pub use self::size::Size; -pub use self::smart::Smart; -pub use self::stroke::{ - DashLength, DashPattern, LineCap, LineJoin, PartialStroke, Stroke, -}; -pub use self::transform::Transform; - -use std::cmp::Ordering; -use std::f64::consts::PI; -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::iter::Sum; -use std::ops::*; - -use crate::diag::{bail, StrResult}; -use crate::eval::{array, cast, Array, Dict, Value}; -use crate::model::{Fold, Resolve, StyleChain}; - -/// Generic access to a structure's components. -pub trait Get<Index> { - /// The structure's component type. - type Component; - - /// Borrow the component for the specified index. - fn get_ref(&self, index: Index) -> &Self::Component; - - /// Borrow the component for the specified index mutably. - fn get_mut(&mut self, index: Index) -> &mut Self::Component; - - /// Convenience method for getting a copy of a component. - fn get(self, index: Index) -> Self::Component - where - Self: Sized, - Self::Component: Copy, - { - *self.get_ref(index) - } - - /// Convenience method for setting a component. - fn set(&mut self, index: Index, component: Self::Component) { - *self.get_mut(index) = component; - } -} - -/// A numeric type. -pub trait Numeric: - Sized - + Debug - + Copy - + PartialEq - + Neg<Output = Self> - + Add<Output = Self> - + Sub<Output = Self> - + Mul<f64, Output = Self> - + Div<f64, Output = Self> -{ - /// The identity element for addition. - fn zero() -> Self; - - /// Whether `self` is zero. - fn is_zero(self) -> bool { - self == Self::zero() - } - - /// Whether `self` consists only of finite parts. - fn is_finite(self) -> bool; -} - -/// Round a float to two decimal places. -pub fn round_2(value: f64) -> f64 { - (value * 100.0).round() / 100.0 -} diff --git a/src/geom/paint.rs b/src/geom/paint.rs deleted file mode 100644 index 10fa9fde..00000000 --- a/src/geom/paint.rs +++ /dev/null @@ -1,30 +0,0 @@ -use super::*; - -/// How a fill or stroke should be painted. -#[derive(Clone, Eq, PartialEq, Hash)] -pub enum Paint { - /// A solid color. - Solid(Color), -} - -impl<T: Into<Color>> From<T> for Paint { - fn from(t: T) -> Self { - Self::Solid(t.into()) - } -} - -impl Debug for Paint { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Solid(color) => color.fmt(f), - } - } -} - -cast! { - Paint, - self => match self { - Self::Solid(color) => Value::Color(color), - }, - color: Color => Self::Solid(color), -} diff --git a/src/geom/path.rs b/src/geom/path.rs deleted file mode 100644 index 1c5325a3..00000000 --- a/src/geom/path.rs +++ /dev/null @@ -1,54 +0,0 @@ -use super::*; - -/// A bezier path. -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct Path(pub Vec<PathItem>); - -/// An item in a bezier path. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum PathItem { - MoveTo(Point), - LineTo(Point), - CubicTo(Point, Point, Point), - ClosePath, -} - -impl Path { - /// Create an empty path. - pub const fn new() -> Self { - Self(vec![]) - } - - /// Create a path that describes a rectangle. - pub fn rect(size: Size) -> Self { - let z = Abs::zero(); - let point = Point::new; - let mut path = Self::new(); - path.move_to(point(z, z)); - path.line_to(point(size.x, z)); - path.line_to(point(size.x, size.y)); - path.line_to(point(z, size.y)); - path.close_path(); - path - } - - /// Push a [`MoveTo`](PathItem::MoveTo) item. - pub fn move_to(&mut self, p: Point) { - self.0.push(PathItem::MoveTo(p)); - } - - /// Push a [`LineTo`](PathItem::LineTo) item. - pub fn line_to(&mut self, p: Point) { - self.0.push(PathItem::LineTo(p)); - } - - /// Push a [`CubicTo`](PathItem::CubicTo) item. - pub fn cubic_to(&mut self, p1: Point, p2: Point, p3: Point) { - self.0.push(PathItem::CubicTo(p1, p2, p3)); - } - - /// Push a [`ClosePath`](PathItem::ClosePath) item. - pub fn close_path(&mut self) { - self.0.push(PathItem::ClosePath); - } -} diff --git a/src/geom/point.rs b/src/geom/point.rs deleted file mode 100644 index e7811e1e..00000000 --- a/src/geom/point.rs +++ /dev/null @@ -1,146 +0,0 @@ -use super::*; - -/// A point in 2D. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Point { - /// The x coordinate. - pub x: Abs, - /// The y coordinate. - pub y: Abs, -} - -impl Point { - /// The origin point. - pub const fn zero() -> Self { - Self { x: Abs::zero(), y: Abs::zero() } - } - - /// Create a new point from x and y coordinates. - pub const fn new(x: Abs, y: Abs) -> Self { - Self { x, y } - } - - /// Create an instance with two equal components. - pub const fn splat(value: Abs) -> Self { - Self { x: value, y: value } - } - - /// Create a new point with y set to zero. - pub const fn with_x(x: Abs) -> Self { - Self { x, y: Abs::zero() } - } - - /// Create a new point with x set to zero. - pub const fn with_y(y: Abs) -> Self { - Self { x: Abs::zero(), y } - } - - /// The component-wise minimum of this and another point. - pub fn min(self, other: Self) -> Self { - Self { x: self.x.min(other.x), y: self.y.min(other.y) } - } - - /// The component-wise minimum of this and another point. - pub fn max(self, other: Self) -> Self { - Self { x: self.x.max(other.x), y: self.y.max(other.y) } - } - - /// The distance between this point and the origin. - pub fn hypot(self) -> Abs { - Abs::raw(self.x.to_raw().hypot(self.y.to_raw())) - } - - /// Transform the point with the given transformation. - pub fn transform(self, ts: Transform) -> Self { - Self::new( - ts.sx.of(self.x) + ts.kx.of(self.y) + ts.tx, - ts.ky.of(self.x) + ts.sy.of(self.y) + ts.ty, - ) - } - - /// Convert to a size. - pub fn to_size(self) -> Size { - Size::new(self.x, self.y) - } -} - -impl Numeric for Point { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.x.is_finite() && self.y.is_finite() - } -} - -impl Get<Axis> for Point { - type Component = Abs; - - fn get_ref(&self, axis: Axis) -> &Abs { - match axis { - Axis::X => &self.x, - Axis::Y => &self.y, - } - } - - fn get_mut(&mut self, axis: Axis) -> &mut Abs { - match axis { - Axis::X => &mut self.x, - Axis::Y => &mut self.y, - } - } -} - -impl Debug for Point { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Point({:?}, {:?})", self.x, self.y) - } -} - -impl Neg for Point { - type Output = Self; - - fn neg(self) -> Self { - Self { x: -self.x, y: -self.y } - } -} - -impl Add for Point { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { x: self.x + other.x, y: self.y + other.y } - } -} - -sub_impl!(Point - Point -> Point); - -impl Mul<f64> for Point { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self { x: self.x * other, y: self.y * other } - } -} - -impl Mul<Point> for f64 { - type Output = Point; - - fn mul(self, other: Point) -> Point { - other * self - } -} - -impl Div<f64> for Point { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self { x: self.x / other, y: self.y / other } - } -} - -assign_impl!(Point += Point); -assign_impl!(Point -= Point); -assign_impl!(Point *= f64); -assign_impl!(Point /= f64); diff --git a/src/geom/ratio.rs b/src/geom/ratio.rs deleted file mode 100644 index fe87dd6c..00000000 --- a/src/geom/ratio.rs +++ /dev/null @@ -1,133 +0,0 @@ -use super::*; - -/// A ratio of a whole. -/// -/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the -/// corresponding [literal](crate::syntax::ast::Numeric). -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Ratio(Scalar); - -impl Ratio { - /// A ratio of `0%` represented as `0.0`. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// A ratio of `100%` represented as `1.0`. - pub const fn one() -> Self { - Self(Scalar(1.0)) - } - - /// Create a new ratio from a value, where `1.0` means `100%`. - pub const fn new(ratio: f64) -> Self { - Self(Scalar(ratio)) - } - - /// Get the underlying ratio. - pub const fn get(self) -> f64 { - (self.0).0 - } - - /// Whether the ratio is zero. - pub fn is_zero(self) -> bool { - self.0 == 0.0 - } - - /// Whether the ratio is one. - pub fn is_one(self) -> bool { - self.0 == 1.0 - } - - /// The absolute value of this ratio. - pub fn abs(self) -> Self { - Self::new(self.get().abs()) - } - - /// Return the ratio of the given `whole`. - pub fn of<T: Numeric>(self, whole: T) -> T { - let resolved = whole * self.get(); - if resolved.is_finite() { - resolved - } else { - T::zero() - } - } -} - -impl Debug for Ratio { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}%", round_2(100.0 * self.get())) - } -} - -impl Neg for Ratio { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Ratio { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Ratio - Ratio -> Ratio); - -impl Mul for Ratio { - type Output = Self; - - fn mul(self, other: Self) -> Self { - Self(self.0 * other.0) - } -} - -impl Mul<f64> for Ratio { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Ratio> for f64 { - type Output = Ratio; - - fn mul(self, other: Ratio) -> Ratio { - other * self - } -} - -impl Div<f64> for Ratio { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div<Ratio> for f64 { - type Output = Self; - - fn div(self, other: Ratio) -> Self { - self / other.get() - } -} - -impl Div for Ratio { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.get() / other.get() - } -} - -assign_impl!(Ratio += Ratio); -assign_impl!(Ratio -= Ratio); -assign_impl!(Ratio *= Ratio); -assign_impl!(Ratio *= f64); -assign_impl!(Ratio /= f64); diff --git a/src/geom/rel.rs b/src/geom/rel.rs deleted file mode 100644 index 88972222..00000000 --- a/src/geom/rel.rs +++ /dev/null @@ -1,246 +0,0 @@ -use super::*; - -/// A value that is composed of a relative and an absolute part. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Rel<T: Numeric> { - /// The relative part. - pub rel: Ratio, - /// The absolute part. - pub abs: T, -} - -impl<T: Numeric> Rel<T> { - /// The zero relative. - pub fn zero() -> Self { - Self { rel: Ratio::zero(), abs: T::zero() } - } - - /// A relative with a ratio of `100%` and no absolute part. - pub fn one() -> Self { - Self { rel: Ratio::one(), abs: T::zero() } - } - - /// Create a new relative from its parts. - pub fn new(rel: Ratio, abs: T) -> Self { - Self { rel, abs } - } - - /// Whether both parts are zero. - pub fn is_zero(self) -> bool { - self.rel.is_zero() && self.abs == T::zero() - } - - /// Whether the relative part is one and the absolute part is zero. - pub fn is_one(self) -> bool { - self.rel.is_one() && self.abs == T::zero() - } - - /// Evaluate this relative to the given `whole`. - pub fn relative_to(self, whole: T) -> T { - self.rel.of(whole) + self.abs - } - - /// Map the absolute part with `f`. - pub fn map<F, U>(self, f: F) -> Rel<U> - where - F: FnOnce(T) -> U, - U: Numeric, - { - Rel { rel: self.rel, abs: f(self.abs) } - } -} - -impl Rel<Length> { - /// Try to divide two relative lengths. - pub fn try_div(self, other: Self) -> Option<f64> { - if self.rel.is_zero() && other.rel.is_zero() { - self.abs.try_div(other.abs) - } else if self.abs.is_zero() && other.abs.is_zero() { - Some(self.rel / other.rel) - } else { - None - } - } -} - -impl<T: Numeric> Debug for Rel<T> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match (self.rel.is_zero(), self.abs.is_zero()) { - (false, false) => write!(f, "{:?} + {:?}", self.rel, self.abs), - (false, true) => self.rel.fmt(f), - (true, _) => self.abs.fmt(f), - } - } -} - -impl From<Abs> for Rel<Length> { - fn from(abs: Abs) -> Self { - Rel::from(Length::from(abs)) - } -} - -impl From<Em> for Rel<Length> { - fn from(em: Em) -> Self { - Rel::from(Length::from(em)) - } -} - -impl<T: Numeric> From<T> for Rel<T> { - fn from(abs: T) -> Self { - Self { rel: Ratio::zero(), abs } - } -} - -impl<T: Numeric> From<Ratio> for Rel<T> { - fn from(rel: Ratio) -> Self { - Self { rel, abs: T::zero() } - } -} - -impl<T: Numeric + PartialOrd> PartialOrd for Rel<T> { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - if self.rel.is_zero() && other.rel.is_zero() { - self.abs.partial_cmp(&other.abs) - } else if self.abs.is_zero() && other.abs.is_zero() { - self.rel.partial_cmp(&other.rel) - } else { - None - } - } -} - -impl<T: Numeric> Neg for Rel<T> { - type Output = Self; - - fn neg(self) -> Self { - Self { rel: -self.rel, abs: -self.abs } - } -} - -impl<T: Numeric> Add for Rel<T> { - type Output = Self; - - fn add(self, other: Self) -> Self::Output { - Self { - rel: self.rel + other.rel, - abs: self.abs + other.abs, - } - } -} - -impl<T: Numeric> Sub for Rel<T> { - type Output = Self; - - fn sub(self, other: Self) -> Self::Output { - self + -other - } -} - -impl<T: Numeric> Mul<f64> for Rel<T> { - type Output = Self; - - fn mul(self, other: f64) -> Self::Output { - Self { rel: self.rel * other, abs: self.abs * other } - } -} - -impl<T: Numeric> Mul<Rel<T>> for f64 { - type Output = Rel<T>; - - fn mul(self, other: Rel<T>) -> Self::Output { - other * self - } -} - -impl<T: Numeric> Div<f64> for Rel<T> { - type Output = Self; - - fn div(self, other: f64) -> Self::Output { - Self { rel: self.rel / other, abs: self.abs / other } - } -} - -impl<T: Numeric + AddAssign> AddAssign for Rel<T> { - fn add_assign(&mut self, other: Self) { - self.rel += other.rel; - self.abs += other.abs; - } -} - -impl<T: Numeric + SubAssign> SubAssign for Rel<T> { - fn sub_assign(&mut self, other: Self) { - self.rel -= other.rel; - self.abs -= other.abs; - } -} - -impl<T: Numeric + MulAssign<f64>> MulAssign<f64> for Rel<T> { - fn mul_assign(&mut self, other: f64) { - self.rel *= other; - self.abs *= other; - } -} - -impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Rel<T> { - fn div_assign(&mut self, other: f64) { - self.rel /= other; - self.abs /= other; - } -} - -impl<T: Numeric> Add<T> for Ratio { - type Output = Rel<T>; - - fn add(self, other: T) -> Self::Output { - Rel::from(self) + Rel::from(other) - } -} - -impl<T: Numeric> Add<T> for Rel<T> { - type Output = Self; - - fn add(self, other: T) -> Self::Output { - self + Rel::from(other) - } -} - -impl<T: Numeric> Add<Ratio> for Rel<T> { - type Output = Self; - - fn add(self, other: Ratio) -> Self::Output { - self + Rel::from(other) - } -} - -impl<T> Resolve for Rel<T> -where - T: Resolve + Numeric, - <T as Resolve>::Output: Numeric, -{ - type Output = Rel<<T as Resolve>::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|abs| abs.resolve(styles)) - } -} - -impl Fold for Rel<Abs> { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -impl Fold for Rel<Length> { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -cast! { - Rel<Abs>, - self => self.map(Length::from).into_value(), -} diff --git a/src/geom/rounded.rs b/src/geom/rounded.rs deleted file mode 100644 index f1a7ea08..00000000 --- a/src/geom/rounded.rs +++ /dev/null @@ -1,182 +0,0 @@ -use super::*; - -/// Produce shapes that together make up a rounded rectangle. -pub fn rounded_rect( - size: Size, - radius: Corners<Abs>, - fill: Option<Paint>, - stroke: Sides<Option<Stroke>>, -) -> Vec<Shape> { - let mut res = vec![]; - if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) { - res.push(Shape { - geometry: fill_geometry(size, radius), - fill, - stroke: if stroke.is_uniform() { stroke.top.clone() } else { None }, - }); - } - - if !stroke.is_uniform() { - for (path, stroke) in stroke_segments(size, radius, stroke) { - if stroke.is_some() { - res.push(Shape { geometry: Geometry::Path(path), fill: None, stroke }); - } - } - } - - res -} - -/// Output the shape of the rectangle as a path or primitive rectangle, -/// depending on whether it is rounded. -fn fill_geometry(size: Size, radius: Corners<Abs>) -> Geometry { - if radius.iter().copied().all(Abs::is_zero) { - Geometry::Rect(size) - } else { - let mut paths = stroke_segments(size, radius, Sides::splat(None)); - assert_eq!(paths.len(), 1); - Geometry::Path(paths.pop().unwrap().0) - } -} - -/// Output the minimum number of paths along the rectangles border. -fn stroke_segments( - size: Size, - radius: Corners<Abs>, - stroke: Sides<Option<Stroke>>, -) -> Vec<(Path, Option<Stroke>)> { - let mut res = vec![]; - - let mut connection = Connection::default(); - let mut path = Path::new(); - let mut always_continuous = true; - let max_radius = size.x.min(size.y).max(Abs::zero()) / 2.0; - - for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] { - let continuous = stroke.get_ref(side) == stroke.get_ref(side.next_cw()); - connection = connection.advance(continuous && side != Side::Left); - always_continuous &= continuous; - - draw_side( - &mut path, - side, - size, - radius.get(side.start_corner()).clamp(Abs::zero(), max_radius), - radius.get(side.end_corner()).clamp(Abs::zero(), max_radius), - connection, - ); - - if !continuous { - res.push((std::mem::take(&mut path), stroke.get_ref(side).clone())); - } - } - - if always_continuous { - path.close_path(); - } - - if !path.0.is_empty() { - res.push((path, stroke.left)); - } - - res -} - -/// Draws one side of the rounded rectangle. Will always draw the left arc. The -/// right arc will be drawn halfway if and only if there is no connection. -fn draw_side( - path: &mut Path, - side: Side, - size: Size, - start_radius: Abs, - end_radius: Abs, - connection: Connection, -) { - let angle_left = Angle::deg(if connection.prev { 90.0 } else { 45.0 }); - let angle_right = Angle::deg(if connection.next { 90.0 } else { 45.0 }); - let length = size.get(side.axis()); - - // The arcs for a border of the rectangle along the x-axis, starting at (0,0). - let p1 = Point::with_x(start_radius); - let mut arc1 = bezier_arc( - p1 + Point::new( - -angle_left.sin() * start_radius, - (1.0 - angle_left.cos()) * start_radius, - ), - Point::new(start_radius, start_radius), - p1, - ); - - let p2 = Point::with_x(length - end_radius); - let mut arc2 = bezier_arc( - p2, - Point::new(length - end_radius, end_radius), - p2 + Point::new( - angle_right.sin() * end_radius, - (1.0 - angle_right.cos()) * end_radius, - ), - ); - - let transform = match side { - Side::Left => Transform::rotate(Angle::deg(-90.0)) - .post_concat(Transform::translate(Abs::zero(), size.y)), - Side::Bottom => Transform::rotate(Angle::deg(180.0)) - .post_concat(Transform::translate(size.x, size.y)), - Side::Right => Transform::rotate(Angle::deg(90.0)) - .post_concat(Transform::translate(size.x, Abs::zero())), - _ => Transform::identity(), - }; - - arc1 = arc1.map(|x| x.transform(transform)); - arc2 = arc2.map(|x| x.transform(transform)); - - if !connection.prev { - path.move_to(if start_radius.is_zero() { arc1[3] } else { arc1[0] }); - } - - if !start_radius.is_zero() { - path.cubic_to(arc1[1], arc1[2], arc1[3]); - } - - path.line_to(arc2[0]); - - if !connection.next && !end_radius.is_zero() { - path.cubic_to(arc2[1], arc2[2], arc2[3]); - } -} - -/// Get the control points for a bezier curve that describes a circular arc for -/// a start point, an end point and a center of the circle whose arc connects -/// the two. -fn bezier_arc(start: Point, center: Point, end: Point) -> [Point; 4] { - // https://stackoverflow.com/a/44829356/1567835 - let a = start - center; - let b = end - center; - - let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw(); - let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw(); - let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2) - / (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw()); - - let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x); - let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x); - - [start, control_1, control_2, end] -} - -/// Indicates which sides of the border strokes in a 2D polygon are connected to -/// their neighboring sides. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -struct Connection { - prev: bool, - next: bool, -} - -impl Connection { - /// Advance to the next clockwise side of the polygon. The argument - /// indicates whether the border is connected on the right side of the next - /// edge. - pub fn advance(self, next: bool) -> Self { - Self { prev: self.next, next } - } -} diff --git a/src/geom/scalar.rs b/src/geom/scalar.rs deleted file mode 100644 index 71fb1755..00000000 --- a/src/geom/scalar.rs +++ /dev/null @@ -1,175 +0,0 @@ -use super::*; - -/// A 64-bit float that implements `Eq`, `Ord` and `Hash`. -/// -/// Panics if it's `NaN` during any of those operations. -#[derive(Default, Copy, Clone)] -pub struct Scalar(pub f64); - -impl Numeric for Scalar { - fn zero() -> Self { - Self(0.0) - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl From<f64> for Scalar { - fn from(float: f64) -> Self { - Self(float) - } -} - -impl From<Scalar> for f64 { - fn from(scalar: Scalar) -> Self { - scalar.0 - } -} - -impl Debug for Scalar { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Eq for Scalar {} - -impl PartialEq for Scalar { - fn eq(&self, other: &Self) -> bool { - assert!(!self.0.is_nan() && !other.0.is_nan(), "float is NaN"); - self.0 == other.0 - } -} - -impl PartialEq<f64> for Scalar { - fn eq(&self, other: &f64) -> bool { - self == &Self(*other) - } -} - -impl Ord for Scalar { - fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).expect("float is NaN") - } -} - -impl PartialOrd for Scalar { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - self.0.partial_cmp(&other.0) - } - - fn lt(&self, other: &Self) -> bool { - self.0 < other.0 - } - - fn le(&self, other: &Self) -> bool { - self.0 <= other.0 - } - - fn gt(&self, other: &Self) -> bool { - self.0 > other.0 - } - - fn ge(&self, other: &Self) -> bool { - self.0 >= other.0 - } -} - -impl Hash for Scalar { - fn hash<H: Hasher>(&self, state: &mut H) { - debug_assert!(!self.0.is_nan(), "float is NaN"); - self.0.to_bits().hash(state); - } -} - -impl Neg for Scalar { - type Output = Self; - - fn neg(self) -> Self::Output { - Self(-self.0) - } -} - -impl<T: Into<Self>> Add<T> for Scalar { - type Output = Self; - - fn add(self, rhs: T) -> Self::Output { - Self(self.0 + rhs.into().0) - } -} - -impl<T: Into<Self>> AddAssign<T> for Scalar { - fn add_assign(&mut self, rhs: T) { - self.0 += rhs.into().0; - } -} - -impl<T: Into<Self>> Sub<T> for Scalar { - type Output = Self; - - fn sub(self, rhs: T) -> Self::Output { - Self(self.0 - rhs.into().0) - } -} - -impl<T: Into<Self>> SubAssign<T> for Scalar { - fn sub_assign(&mut self, rhs: T) { - self.0 -= rhs.into().0; - } -} - -impl<T: Into<Self>> Mul<T> for Scalar { - type Output = Self; - - fn mul(self, rhs: T) -> Self::Output { - Self(self.0 * rhs.into().0) - } -} - -impl<T: Into<Self>> MulAssign<T> for Scalar { - fn mul_assign(&mut self, rhs: T) { - self.0 *= rhs.into().0; - } -} - -impl<T: Into<Self>> Div<T> for Scalar { - type Output = Self; - - fn div(self, rhs: T) -> Self::Output { - Self(self.0 / rhs.into().0) - } -} - -impl<T: Into<Self>> DivAssign<T> for Scalar { - fn div_assign(&mut self, rhs: T) { - self.0 /= rhs.into().0; - } -} - -impl<T: Into<Self>> Rem<T> for Scalar { - type Output = Self; - - fn rem(self, rhs: T) -> Self::Output { - Self(self.0 % rhs.into().0) - } -} - -impl<T: Into<Self>> RemAssign<T> for Scalar { - fn rem_assign(&mut self, rhs: T) { - self.0 %= rhs.into().0; - } -} - -impl Sum for Scalar { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -impl<'a> Sum<&'a Self> for Scalar { - fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} diff --git a/src/geom/shape.rs b/src/geom/shape.rs deleted file mode 100644 index 5658c21f..00000000 --- a/src/geom/shape.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::*; - -/// A geometric shape with optional fill and stroke. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Shape { - /// The shape's geometry. - pub geometry: Geometry, - /// The shape's background fill. - pub fill: Option<Paint>, - /// The shape's border stroke. - pub stroke: Option<Stroke>, -} - -/// A shape's geometry. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Geometry { - /// A line to a point (relative to its position). - Line(Point), - /// A rectangle with its origin in the topleft corner. - Rect(Size), - /// A bezier path. - Path(Path), -} - -impl Geometry { - /// Fill the geometry without a stroke. - pub fn filled(self, fill: Paint) -> Shape { - Shape { geometry: self, fill: Some(fill), stroke: None } - } - - /// Stroke the geometry without a fill. - pub fn stroked(self, stroke: Stroke) -> Shape { - Shape { geometry: self, fill: None, stroke: Some(stroke) } - } -} diff --git a/src/geom/sides.rs b/src/geom/sides.rs deleted file mode 100644 index d4b72a9d..00000000 --- a/src/geom/sides.rs +++ /dev/null @@ -1,268 +0,0 @@ -use crate::eval::{CastInfo, FromValue, IntoValue, Reflect}; - -use super::*; - -/// A container with left, top, right and bottom components. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Sides<T> { - /// The value for the left side. - pub left: T, - /// The value for the top side. - pub top: T, - /// The value for the right side. - pub right: T, - /// The value for the bottom side. - pub bottom: T, -} - -impl<T> Sides<T> { - /// Create a new instance from the four components. - pub const fn new(left: T, top: T, right: T, bottom: T) -> Self { - Self { left, top, right, bottom } - } - - /// Create an instance with four equal components. - pub fn splat(value: T) -> Self - where - T: Clone, - { - Self { - left: value.clone(), - top: value.clone(), - right: value.clone(), - bottom: value, - } - } - - /// Map the individual fields with `f`. - pub fn map<F, U>(self, mut f: F) -> Sides<U> - where - F: FnMut(T) -> U, - { - Sides { - left: f(self.left), - top: f(self.top), - right: f(self.right), - bottom: f(self.bottom), - } - } - - /// Zip two instances into one. - pub fn zip<U>(self, other: Sides<U>) -> Sides<(T, U)> { - Sides { - left: (self.left, other.left), - top: (self.top, other.top), - right: (self.right, other.right), - bottom: (self.bottom, other.bottom), - } - } - - /// An iterator over the sides, starting with the left side, clockwise. - pub fn iter(&self) -> impl Iterator<Item = &T> { - [&self.left, &self.top, &self.right, &self.bottom].into_iter() - } - - /// Whether all sides are equal. - pub fn is_uniform(&self) -> bool - where - T: PartialEq, - { - self.left == self.top && self.top == self.right && self.right == self.bottom - } -} - -impl<T: Add> Sides<T> { - /// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`. - pub fn sum_by_axis(self) -> Axes<T::Output> { - Axes::new(self.left + self.right, self.top + self.bottom) - } -} - -impl Sides<Rel<Abs>> { - /// Evaluate the sides relative to the given `size`. - pub fn relative_to(self, size: Size) -> Sides<Abs> { - Sides { - left: self.left.relative_to(size.x), - top: self.top.relative_to(size.y), - right: self.right.relative_to(size.x), - bottom: self.bottom.relative_to(size.y), - } - } -} - -impl<T> Get<Side> for Sides<T> { - type Component = T; - - fn get_ref(&self, side: Side) -> &T { - match side { - Side::Left => &self.left, - Side::Top => &self.top, - Side::Right => &self.right, - Side::Bottom => &self.bottom, - } - } - - fn get_mut(&mut self, side: Side) -> &mut T { - match side { - Side::Left => &mut self.left, - Side::Top => &mut self.top, - Side::Right => &mut self.right, - Side::Bottom => &mut self.bottom, - } - } -} - -/// The four sides of objects. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Side { - /// The left side. - Left, - /// The top side. - Top, - /// The right side. - Right, - /// The bottom side. - Bottom, -} - -impl Side { - /// The opposite side. - pub fn inv(self) -> Self { - match self { - Self::Left => Self::Right, - Self::Top => Self::Bottom, - Self::Right => Self::Left, - Self::Bottom => Self::Top, - } - } - - /// The next side, clockwise. - pub fn next_cw(self) -> Self { - match self { - Self::Left => Self::Top, - Self::Top => Self::Right, - Self::Right => Self::Bottom, - Self::Bottom => Self::Left, - } - } - - /// The next side, counter-clockwise. - pub fn next_ccw(self) -> Self { - match self { - Self::Left => Self::Bottom, - Self::Top => Self::Left, - Self::Right => Self::Top, - Self::Bottom => Self::Right, - } - } - - /// The first corner of the side in clockwise order. - pub fn start_corner(self) -> Corner { - match self { - Self::Left => Corner::BottomLeft, - Self::Top => Corner::TopLeft, - Self::Right => Corner::TopRight, - Self::Bottom => Corner::BottomRight, - } - } - - /// The second corner of the side in clockwise order. - pub fn end_corner(self) -> Corner { - self.next_cw().start_corner() - } - - /// Return the corresponding axis. - pub fn axis(self) -> Axis { - match self { - Self::Left | Self::Right => Axis::Y, - Self::Top | Self::Bottom => Axis::X, - } - } -} - -impl<T: Reflect> Reflect for Sides<Option<T>> { - fn describe() -> CastInfo { - T::describe() + Dict::describe() - } - - fn castable(value: &Value) -> bool { - Dict::castable(value) || T::castable(value) - } -} - -impl<T> IntoValue for Sides<T> -where - T: PartialEq + IntoValue, -{ - fn into_value(self) -> Value { - if self.is_uniform() { - return self.left.into_value(); - } - - let mut dict = Dict::new(); - let mut handle = |key: &str, component: T| { - let value = component.into_value(); - if value != Value::None { - dict.insert(key.into(), value); - } - }; - - handle("left", self.left); - handle("top", self.top); - handle("right", self.right); - handle("bottom", self.bottom); - - Value::Dict(dict) - } -} - -impl<T> FromValue for Sides<Option<T>> -where - T: Default + FromValue + Clone, -{ - fn from_value(mut value: Value) -> StrResult<Self> { - let keys = ["left", "top", "right", "bottom", "x", "y", "rest"]; - if let Value::Dict(dict) = &mut value { - if dict.iter().any(|(key, _)| keys.contains(&key.as_str())) { - let mut take = |key| dict.take(key).ok().map(T::from_value).transpose(); - let rest = take("rest")?; - let x = take("x")?.or_else(|| rest.clone()); - let y = take("y")?.or_else(|| rest.clone()); - let sides = Sides { - left: take("left")?.or_else(|| x.clone()), - top: take("top")?.or_else(|| y.clone()), - right: take("right")?.or_else(|| x.clone()), - bottom: take("bottom")?.or_else(|| y.clone()), - }; - - dict.finish(&keys)?; - return Ok(sides); - } - } - - if T::castable(&value) { - Ok(Self::splat(Some(T::from_value(value)?))) - } else { - Err(Self::error(&value)) - } - } -} - -impl<T: Resolve> Resolve for Sides<T> { - type Output = Sides<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T: Fold> Fold for Sides<Option<T>> { - type Output = Sides<T::Output>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer).map(|(inner, outer)| match inner { - Some(value) => value.fold(outer), - None => outer, - }) - } -} diff --git a/src/geom/size.rs b/src/geom/size.rs deleted file mode 100644 index a2e32b77..00000000 --- a/src/geom/size.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::*; - -/// A size in 2D. -pub type Size = Axes<Abs>; - -impl Size { - /// The zero value. - pub const fn zero() -> Self { - Self { x: Abs::zero(), y: Abs::zero() } - } - - /// Whether the other size fits into this one (smaller width and height). - pub fn fits(self, other: Self) -> bool { - self.x.fits(other.x) && self.y.fits(other.y) - } - - /// Convert to a point. - pub fn to_point(self) -> Point { - Point::new(self.x, self.y) - } -} - -impl Numeric for Size { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.x.is_finite() && self.y.is_finite() - } -} - -impl Neg for Size { - type Output = Self; - - fn neg(self) -> Self { - Self { x: -self.x, y: -self.y } - } -} - -impl Add for Size { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { x: self.x + other.x, y: self.y + other.y } - } -} - -sub_impl!(Size - Size -> Size); - -impl Mul<f64> for Size { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self { x: self.x * other, y: self.y * other } - } -} - -impl Mul<Size> for f64 { - type Output = Size; - - fn mul(self, other: Size) -> Size { - other * self - } -} - -impl Div<f64> for Size { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self { x: self.x / other, y: self.y / other } - } -} - -assign_impl!(Size -= Size); -assign_impl!(Size += Size); -assign_impl!(Size *= f64); -assign_impl!(Size /= f64); diff --git a/src/geom/smart.rs b/src/geom/smart.rs deleted file mode 100644 index a6271c20..00000000 --- a/src/geom/smart.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::eval::{AutoValue, CastInfo, FromValue, IntoValue, Reflect}; - -use super::*; - -/// A value that can be automatically determined. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum Smart<T> { - /// The value should be determined smartly based on the circumstances. - Auto, - /// A specific value. - Custom(T), -} - -impl<T> Smart<T> { - /// Whether the value is `Auto`. - pub fn is_auto(&self) -> bool { - matches!(self, Self::Auto) - } - - /// Whether this holds a custom value. - pub fn is_custom(&self) -> bool { - matches!(self, Self::Custom(_)) - } - - /// Returns a reference the contained custom value. - /// If the value is [`Smart::Auto`], `None` is returned. - pub fn as_custom(self) -> Option<T> { - match self { - Self::Auto => None, - Self::Custom(x) => Some(x), - } - } - - /// Map the contained custom value with `f`. - pub fn map<F, U>(self, f: F) -> Smart<U> - where - F: FnOnce(T) -> U, - { - match self { - Self::Auto => Smart::Auto, - Self::Custom(x) => Smart::Custom(f(x)), - } - } - - /// Map the contained custom value with `f` if it contains a custom value, - /// otherwise returns `default`. - pub fn map_or<F, U>(self, default: U, f: F) -> U - where - F: FnOnce(T) -> U, - { - match self { - Self::Auto => default, - Self::Custom(x) => f(x), - } - } - - /// Keeps `self` if it contains a custom value, otherwise returns `other`. - pub fn or(self, other: Smart<T>) -> Self { - match self { - Self::Custom(x) => Self::Custom(x), - Self::Auto => other, - } - } - - /// Returns the contained custom value or a provided default value. - pub fn unwrap_or(self, default: T) -> T { - match self { - Self::Auto => default, - Self::Custom(x) => x, - } - } - - /// Returns the contained custom value or computes a default value. - pub fn unwrap_or_else<F>(self, f: F) -> T - where - F: FnOnce() -> T, - { - match self { - Self::Auto => f(), - Self::Custom(x) => x, - } - } - - /// Returns the contained custom value or the default value. - pub fn unwrap_or_default(self) -> T - where - T: Default, - { - self.unwrap_or_else(T::default) - } -} - -impl<T> Default for Smart<T> { - fn default() -> Self { - Self::Auto - } -} - -impl<T: Reflect> Reflect for Smart<T> { - fn castable(value: &Value) -> bool { - AutoValue::castable(value) || T::castable(value) - } - - fn describe() -> CastInfo { - T::describe() + AutoValue::describe() - } -} - -impl<T: IntoValue> IntoValue for Smart<T> { - fn into_value(self) -> Value { - match self { - Smart::Custom(v) => v.into_value(), - Smart::Auto => Value::Auto, - } - } -} - -impl<T: FromValue> FromValue for Smart<T> { - fn from_value(value: Value) -> StrResult<Self> { - match value { - Value::Auto => Ok(Self::Auto), - v if T::castable(&v) => Ok(Self::Custom(T::from_value(v)?)), - _ => Err(Self::error(&value)), - } - } -} - -impl<T: Resolve> Resolve for Smart<T> { - type Output = Smart<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T> Fold for Smart<T> -where - T: Fold, - T::Output: Default, -{ - type Output = Smart<T::Output>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) - } -} diff --git a/src/geom/stroke.rs b/src/geom/stroke.rs deleted file mode 100644 index 66264d5d..00000000 --- a/src/geom/stroke.rs +++ /dev/null @@ -1,387 +0,0 @@ -use crate::eval::{Cast, FromValue}; - -use super::*; - -/// A stroke of a geometric shape. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Stroke { - /// The stroke's paint. - pub paint: Paint, - /// The stroke's thickness. - pub thickness: Abs, - /// The stroke's line cap. - pub line_cap: LineCap, - /// The stroke's line join. - pub line_join: LineJoin, - /// The stroke's line dash pattern. - pub dash_pattern: Option<DashPattern<Abs, Abs>>, - /// The miter limit. Defaults to 4.0, same as `tiny-skia`. - pub miter_limit: Scalar, -} - -impl Default for Stroke { - fn default() -> Self { - Self { - paint: Paint::Solid(Color::BLACK), - thickness: Abs::pt(1.0), - line_cap: LineCap::Butt, - line_join: LineJoin::Miter, - dash_pattern: None, - miter_limit: Scalar(4.0), - } - } -} - -/// A partial stroke representation. -/// -/// In this representation, both fields are optional so that you can pass either -/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where -/// this is expected. -#[derive(Default, Clone, Eq, PartialEq, Hash)] -pub struct PartialStroke<T = Length> { - /// The stroke's paint. - pub paint: Smart<Paint>, - /// The stroke's thickness. - pub thickness: Smart<T>, - /// The stroke's line cap. - pub line_cap: Smart<LineCap>, - /// The stroke's line join. - pub line_join: Smart<LineJoin>, - /// The stroke's line dash pattern. - pub dash_pattern: Smart<Option<DashPattern<T>>>, - /// The miter limit. - pub miter_limit: Smart<Scalar>, -} - -impl<T> PartialStroke<T> { - /// Map the contained lengths with `f`. - pub fn map<F, U>(self, f: F) -> PartialStroke<U> - where - F: Fn(T) -> U, - { - PartialStroke { - paint: self.paint, - thickness: self.thickness.map(&f), - line_cap: self.line_cap, - line_join: self.line_join, - dash_pattern: self.dash_pattern.map(|pattern| { - pattern.map(|pattern| DashPattern { - array: pattern - .array - .into_iter() - .map(|l| match l { - DashLength::Length(v) => DashLength::Length(f(v)), - DashLength::LineWidth => DashLength::LineWidth, - }) - .collect(), - phase: f(pattern.phase), - }) - }), - miter_limit: self.miter_limit, - } - } -} - -impl PartialStroke<Abs> { - /// Unpack the stroke, filling missing fields from the `default`. - pub fn unwrap_or(self, default: Stroke) -> Stroke { - let thickness = self.thickness.unwrap_or(default.thickness); - let dash_pattern = self - .dash_pattern - .map(|pattern| { - pattern.map(|pattern| DashPattern { - array: pattern - .array - .into_iter() - .map(|l| l.finish(thickness)) - .collect(), - phase: pattern.phase, - }) - }) - .unwrap_or(default.dash_pattern); - - Stroke { - paint: self.paint.unwrap_or(default.paint), - thickness, - line_cap: self.line_cap.unwrap_or(default.line_cap), - line_join: self.line_join.unwrap_or(default.line_join), - dash_pattern, - miter_limit: self.miter_limit.unwrap_or(default.miter_limit), - } - } - - /// Unpack the stroke, filling missing fields with the default values. - pub fn unwrap_or_default(self) -> Stroke { - self.unwrap_or(Stroke::default()) - } -} - -impl<T: Debug> Debug for PartialStroke<T> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let Self { - paint, - thickness, - line_cap, - line_join, - dash_pattern, - miter_limit, - } = &self; - if line_cap.is_auto() - && line_join.is_auto() - && dash_pattern.is_auto() - && miter_limit.is_auto() - { - match (&self.paint, &self.thickness) { - (Smart::Custom(paint), Smart::Custom(thickness)) => { - write!(f, "{thickness:?} + {paint:?}") - } - (Smart::Custom(paint), Smart::Auto) => paint.fmt(f), - (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f), - (Smart::Auto, Smart::Auto) => f.pad("1pt + black"), - } - } else { - write!(f, "(")?; - let mut sep = ""; - if let Smart::Custom(paint) = &paint { - write!(f, "{}paint: {:?}", sep, paint)?; - sep = ", "; - } - if let Smart::Custom(thickness) = &thickness { - write!(f, "{}thickness: {:?}", sep, thickness)?; - sep = ", "; - } - if let Smart::Custom(cap) = &line_cap { - write!(f, "{}cap: {:?}", sep, cap)?; - sep = ", "; - } - if let Smart::Custom(join) = &line_join { - write!(f, "{}join: {:?}", sep, join)?; - sep = ", "; - } - if let Smart::Custom(dash) = &dash_pattern { - write!(f, "{}dash: {:?}", sep, dash)?; - sep = ", "; - } - if let Smart::Custom(miter_limit) = &miter_limit { - write!(f, "{}miter-limit: {:?}", sep, miter_limit)?; - } - write!(f, ")")?; - Ok(()) - } - } -} - -impl Resolve for PartialStroke { - type Output = PartialStroke<Abs>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - PartialStroke { - paint: self.paint, - thickness: self.thickness.resolve(styles), - line_cap: self.line_cap, - line_join: self.line_join, - dash_pattern: self.dash_pattern.resolve(styles), - miter_limit: self.miter_limit, - } - } -} - -impl Fold for PartialStroke<Abs> { - type Output = Self; - - fn fold(self, outer: Self::Output) -> Self::Output { - Self { - paint: self.paint.or(outer.paint), - thickness: self.thickness.or(outer.thickness), - line_cap: self.line_cap.or(outer.line_cap), - line_join: self.line_join.or(outer.line_join), - dash_pattern: self.dash_pattern.or(outer.dash_pattern), - miter_limit: self.miter_limit.or(outer.miter_limit), - } - } -} - -cast! { - type PartialStroke: "stroke", - thickness: Length => Self { - thickness: Smart::Custom(thickness), - ..Default::default() - }, - color: Color => Self { - paint: Smart::Custom(color.into()), - ..Default::default() - }, - mut dict: Dict => { - fn take<T: FromValue>(dict: &mut Dict, key: &str) -> StrResult<Smart<T>> { - Ok(dict.take(key).ok().map(T::from_value) - .transpose()?.map(Smart::Custom).unwrap_or(Smart::Auto)) - } - - let paint = take::<Paint>(&mut dict, "paint")?; - let thickness = take::<Length>(&mut dict, "thickness")?; - let line_cap = take::<LineCap>(&mut dict, "cap")?; - let line_join = take::<LineJoin>(&mut dict, "join")?; - let dash_pattern = take::<Option<DashPattern>>(&mut dict, "dash")?; - let miter_limit = take::<f64>(&mut dict, "miter-limit")?; - dict.finish(&["paint", "thickness", "cap", "join", "dash", "miter-limit"])?; - - Self { - paint, - thickness, - line_cap, - line_join, - dash_pattern, - miter_limit: miter_limit.map(Scalar), - } - }, -} - -cast! { - PartialStroke<Abs>, - self => self.map(Length::from).into_value(), -} - -/// The line cap of a stroke -#[derive(Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum LineCap { - Butt, - Round, - Square, -} - -impl Debug for LineCap { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - LineCap::Butt => write!(f, "\"butt\""), - LineCap::Round => write!(f, "\"round\""), - LineCap::Square => write!(f, "\"square\""), - } - } -} - -/// The line join of a stroke -#[derive(Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum LineJoin { - Miter, - Round, - Bevel, -} - -impl Debug for LineJoin { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - LineJoin::Miter => write!(f, "\"miter\""), - LineJoin::Round => write!(f, "\"round\""), - LineJoin::Bevel => write!(f, "\"bevel\""), - } - } -} - -/// A line dash pattern. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct DashPattern<T = Length, DT = DashLength<T>> { - /// The dash array. - pub array: Vec<DT>, - /// The dash phase. - pub phase: T, -} - -impl<T: Debug, DT: Debug> Debug for DashPattern<T, DT> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "(array: (")?; - for (i, elem) in self.array.iter().enumerate() { - if i == 0 { - write!(f, "{:?}", elem)?; - } else { - write!(f, ", {:?}", elem)?; - } - } - write!(f, "), phase: {:?})", self.phase)?; - Ok(()) - } -} - -impl<T: Default> From<Vec<DashLength<T>>> for DashPattern<T> { - fn from(array: Vec<DashLength<T>>) -> Self { - Self { array, phase: T::default() } - } -} - -impl Resolve for DashPattern { - type Output = DashPattern<Abs>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - DashPattern { - array: self.array.into_iter().map(|l| l.resolve(styles)).collect(), - phase: self.phase.resolve(styles), - } - } -} - -// Same names as tikz: -// https://tex.stackexchange.com/questions/45275/tikz-get-values-for-predefined-dash-patterns -cast! { - DashPattern, - - "solid" => Vec::new().into(), - "dotted" => vec![DashLength::LineWidth, Abs::pt(2.0).into()].into(), - "densely-dotted" => vec![DashLength::LineWidth, Abs::pt(1.0).into()].into(), - "loosely-dotted" => vec![DashLength::LineWidth, Abs::pt(4.0).into()].into(), - "dashed" => vec![Abs::pt(3.0).into(), Abs::pt(3.0).into()].into(), - "densely-dashed" => vec![Abs::pt(3.0).into(), Abs::pt(2.0).into()].into(), - "loosely-dashed" => vec![Abs::pt(3.0).into(), Abs::pt(6.0).into()].into(), - "dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(2.0).into(), DashLength::LineWidth, Abs::pt(2.0).into()].into(), - "densely-dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(1.0).into(), DashLength::LineWidth, Abs::pt(1.0).into()].into(), - "loosely-dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(4.0).into(), DashLength::LineWidth, Abs::pt(4.0).into()].into(), - - array: Vec<DashLength> => Self { array, phase: Length::zero() }, - mut dict: Dict => { - let array: Vec<DashLength> = dict.take("array")?.cast()?; - let phase = dict.take("phase").ok().map(Value::cast) - .transpose()?.unwrap_or(Length::zero()); - dict.finish(&["array", "phase"])?; - Self { - array, - phase, - } - }, -} - -/// The length of a dash in a line dash pattern -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum DashLength<T = Length> { - LineWidth, - Length(T), -} - -impl From<Abs> for DashLength { - fn from(l: Abs) -> Self { - DashLength::Length(l.into()) - } -} - -impl<T> DashLength<T> { - fn finish(self, line_width: T) -> T { - match self { - Self::LineWidth => line_width, - Self::Length(l) => l, - } - } -} - -impl Resolve for DashLength { - type Output = DashLength<Abs>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - match self { - Self::LineWidth => DashLength::LineWidth, - Self::Length(v) => DashLength::Length(v.resolve(styles)), - } - } -} - -cast! { - DashLength, - "dot" => Self::LineWidth, - v: Length => Self::Length(v), -} diff --git a/src/geom/transform.rs b/src/geom/transform.rs deleted file mode 100644 index 1ff1dfdd..00000000 --- a/src/geom/transform.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::*; - -/// A scale-skew-translate transformation. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Transform { - pub sx: Ratio, - pub ky: Ratio, - pub kx: Ratio, - pub sy: Ratio, - pub tx: Abs, - pub ty: Abs, -} - -impl Transform { - /// The identity transformation. - pub const fn identity() -> Self { - Self { - sx: Ratio::one(), - ky: Ratio::zero(), - kx: Ratio::zero(), - sy: Ratio::one(), - tx: Abs::zero(), - ty: Abs::zero(), - } - } - - /// A translate transform. - pub const fn translate(tx: Abs, ty: Abs) -> Self { - Self { tx, ty, ..Self::identity() } - } - - /// A scale transform. - pub const fn scale(sx: Ratio, sy: Ratio) -> Self { - Self { sx, sy, ..Self::identity() } - } - - /// A rotate transform. - pub fn rotate(angle: Angle) -> Self { - let cos = Ratio::new(angle.cos()); - let sin = Ratio::new(angle.sin()); - Self { - sx: cos, - ky: sin, - kx: -sin, - sy: cos, - ..Self::default() - } - } - - /// Whether this is the identity transformation. - pub fn is_identity(self) -> bool { - self == Self::identity() - } - - /// Pre-concatenate another transformation. - pub fn pre_concat(self, prev: Self) -> Self { - Transform { - sx: self.sx * prev.sx + self.kx * prev.ky, - ky: self.ky * prev.sx + self.sy * prev.ky, - kx: self.sx * prev.kx + self.kx * prev.sy, - sy: self.ky * prev.kx + self.sy * prev.sy, - tx: self.sx.of(prev.tx) + self.kx.of(prev.ty) + self.tx, - ty: self.ky.of(prev.tx) + self.sy.of(prev.ty) + self.ty, - } - } - - /// Post-concatenate another transformation. - pub fn post_concat(self, next: Self) -> Self { - next.pre_concat(self) - } -} - -impl Default for Transform { - fn default() -> Self { - Self::identity() - } -} diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs deleted file mode 100644 index dad466c1..00000000 --- a/src/ide/analyze.rs +++ /dev/null @@ -1,111 +0,0 @@ -use comemo::Track; -use ecow::EcoString; - -use crate::doc::Frame; -use crate::eval::{eval, Module, Route, Tracer, Value}; -use crate::model::{Introspector, Label}; -use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; -use crate::World; - -/// Try to determine a set of possible values for an expression. -pub fn analyze_expr(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> { - match node.cast::<ast::Expr>() { - Some(ast::Expr::None(_)) => vec![Value::None], - Some(ast::Expr::Auto(_)) => vec![Value::Auto], - Some(ast::Expr::Bool(v)) => vec![Value::Bool(v.get())], - Some(ast::Expr::Int(v)) => vec![Value::Int(v.get())], - Some(ast::Expr::Float(v)) => vec![Value::Float(v.get())], - Some(ast::Expr::Numeric(v)) => vec![Value::numeric(v.get())], - Some(ast::Expr::Str(v)) => vec![Value::Str(v.get().into())], - - Some(ast::Expr::FieldAccess(access)) => { - let Some(child) = node.children().next() else { return vec![] }; - analyze_expr(world, &child) - .into_iter() - .filter_map(|target| target.field(&access.field()).ok()) - .collect() - } - - Some(_) => { - if let Some(parent) = node.parent() { - if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 { - return analyze_expr(world, parent); - } - } - - let route = Route::default(); - let mut tracer = Tracer::new(Some(node.span())); - typst::eval::eval( - world.track(), - route.track(), - tracer.track_mut(), - &world.main(), - ) - .and_then(|module| { - typst::model::typeset( - world.track(), - tracer.track_mut(), - &module.content(), - ) - }) - .ok(); - - tracer.finish() - } - - _ => vec![], - } -} - -/// Try to load a module from the current source file. -pub fn analyze_import( - world: &(dyn World + 'static), - source: &Source, - path: &str, -) -> Option<Module> { - let route = Route::default(); - let mut tracer = Tracer::default(); - let id = source.id().join(path).ok()?; - let source = world.source(id).ok()?; - eval(world.track(), route.track(), tracer.track_mut(), &source).ok() -} - -/// Find all labels and details for them. -/// -/// Returns: -/// - All labels and descriptions for them, if available -/// - A split offset: All labels before this offset belong to nodes, all after -/// belong to a bibliography. -pub fn analyze_labels( - world: &(dyn World + 'static), - frames: &[Frame], -) -> (Vec<(Label, Option<EcoString>)>, usize) { - let mut output = vec![]; - let introspector = Introspector::new(frames); - let items = &world.library().items; - - // Labels in the document. - for elem in introspector.all() { - let Some(label) = elem.label().cloned() else { continue }; - let details = elem - .field("caption") - .or_else(|| elem.field("body")) - .and_then(|field| match field { - Value::Content(content) => Some(content), - _ => None, - }) - .as_ref() - .unwrap_or(elem) - .plain_text(); - output.push((label, Some(details))); - } - - let split = output.len(); - - // Bibliography keys. - for (key, detail) in (items.bibliography_keys)(introspector.track()) { - output.push((Label(key), detail)); - } - - (output, split) -} diff --git a/src/ide/complete.rs b/src/ide/complete.rs deleted file mode 100644 index 16cba1bc..00000000 --- a/src/ide/complete.rs +++ /dev/null @@ -1,1201 +0,0 @@ -use std::collections::{BTreeSet, HashSet}; - -use ecow::{eco_format, EcoString}; -use if_chain::if_chain; -use unscanny::Scanner; - -use super::analyze::analyze_labels; -use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family}; -use crate::doc::Frame; -use crate::eval::{format_str, methods_on, CastInfo, Library, Scope, Value}; -use crate::syntax::{ - ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind, -}; -use crate::util::separated_list; -use crate::World; - -/// Autocomplete a cursor position in a source file. -/// -/// Returns the position from which the completions apply and a list of -/// completions. -/// -/// When `explicit` is `true`, the user requested the completion by pressing -/// control and space or something similar. -pub fn autocomplete( - world: &(dyn World + 'static), - frames: &[Frame], - source: &Source, - cursor: usize, - explicit: bool, -) -> Option<(usize, Vec<Completion>)> { - let mut ctx = CompletionContext::new(world, frames, source, cursor, explicit)?; - - let _ = complete_comments(&mut ctx) - || complete_field_accesses(&mut ctx) - || complete_imports(&mut ctx) - || complete_rules(&mut ctx) - || complete_params(&mut ctx) - || complete_markup(&mut ctx) - || complete_math(&mut ctx) - || complete_code(&mut ctx); - - Some((ctx.from, ctx.completions)) -} - -/// An autocompletion option. -#[derive(Debug, Clone)] -pub struct Completion { - /// The kind of item this completes to. - pub kind: CompletionKind, - /// The label the completion is shown with. - pub label: EcoString, - /// The completed version of the input, possibly described with snippet - /// syntax like `${lhs} + ${rhs}`. - /// - /// Should default to the `label` if `None`. - pub apply: Option<EcoString>, - /// An optional short description, at most one sentence. - pub detail: Option<EcoString>, -} - -/// A kind of item that can be completed. -#[derive(Debug, Clone)] -pub enum CompletionKind { - /// A syntactical structure. - Syntax, - /// A function. - Func, - /// A function parameter. - Param, - /// A constant. - Constant, - /// A symbol. - Symbol(char), -} - -/// Complete in comments. Or rather, don't! -fn complete_comments(ctx: &mut CompletionContext) -> bool { - matches!(ctx.leaf.kind(), SyntaxKind::LineComment | SyntaxKind::BlockComment) -} - -/// Complete in markup mode. -fn complete_markup(ctx: &mut CompletionContext) -> bool { - // Bail if we aren't even in markup. - if !matches!( - ctx.leaf.parent_kind(), - None | Some(SyntaxKind::Markup) | Some(SyntaxKind::Ref) - ) { - return false; - } - - // Start of an interpolated identifier: "#|". - if ctx.leaf.kind() == SyntaxKind::Hashtag { - ctx.from = ctx.cursor; - code_completions(ctx, true); - return true; - } - - // An existing identifier: "#pa|". - if ctx.leaf.kind() == SyntaxKind::Ident { - ctx.from = ctx.leaf.offset(); - code_completions(ctx, true); - return true; - } - - // Start of an reference: "@|" or "@he|". - if ctx.leaf.kind() == SyntaxKind::RefMarker { - ctx.from = ctx.leaf.offset() + 1; - ctx.label_completions(); - return true; - } - - // Behind a half-completed binding: "#let x = |". - if_chain! { - if let Some(prev) = ctx.leaf.prev_leaf(); - if prev.kind() == SyntaxKind::Eq; - if prev.parent_kind() == Some(SyntaxKind::LetBinding); - then { - ctx.from = ctx.cursor; - code_completions(ctx, false); - return true; - } - } - - // Directly after a raw block. - let mut s = Scanner::new(ctx.text); - s.jump(ctx.leaf.offset()); - if s.eat_if("```") { - s.eat_while('`'); - let start = s.cursor(); - if s.eat_if(is_id_start) { - s.eat_while(is_id_continue); - } - if s.cursor() == ctx.cursor { - ctx.from = start; - ctx.raw_completions(); - } - return true; - } - - // Anywhere: "|". - if ctx.explicit { - ctx.from = ctx.cursor; - markup_completions(ctx); - return true; - } - - false -} - -/// Add completions for markup snippets. -#[rustfmt::skip] -fn markup_completions(ctx: &mut CompletionContext) { - ctx.snippet_completion( - "expression", - "#${}", - "Variables, function calls, blocks, and more.", - ); - - ctx.snippet_completion( - "linebreak", - "\\\n${}", - "Inserts a forced linebreak.", - ); - - ctx.snippet_completion( - "strong text", - "*${strong}*", - "Strongly emphasizes content by increasing the font weight.", - ); - - ctx.snippet_completion( - "emphasized text", - "_${emphasized}_", - "Emphasizes content by setting it in italic font style.", - ); - - ctx.snippet_completion( - "raw text", - "`${text}`", - "Displays text verbatim, in monospace.", - ); - - ctx.snippet_completion( - "code listing", - "```${lang}\n${code}\n```", - "Inserts computer code with syntax highlighting.", - ); - - ctx.snippet_completion( - "hyperlink", - "https://${example.com}", - "Links to a URL.", - ); - - ctx.snippet_completion( - "label", - "<${name}>", - "Makes the preceding element referenceable.", - ); - - ctx.snippet_completion( - "reference", - "@${name}", - "Inserts a reference to a label.", - ); - - ctx.snippet_completion( - "heading", - "= ${title}", - "Inserts a section heading.", - ); - - ctx.snippet_completion( - "list item", - "- ${item}", - "Inserts an item of a bullet list.", - ); - - ctx.snippet_completion( - "enumeration item", - "+ ${item}", - "Inserts an item of a numbered list.", - ); - - ctx.snippet_completion( - "enumeration item (numbered)", - "${number}. ${item}", - "Inserts an explicitly numbered list item.", - ); - - ctx.snippet_completion( - "term list item", - "/ ${term}: ${description}", - "Inserts an item of a term list.", - ); - - ctx.snippet_completion( - "math (inline)", - "$${x}$", - "Inserts an inline-level mathematical equation.", - ); - - ctx.snippet_completion( - "math (block)", - "$ ${sum_x^2} $", - "Inserts a block-level mathematical equation.", - ); -} - -/// Complete in math mode. -fn complete_math(ctx: &mut CompletionContext) -> bool { - if !matches!( - ctx.leaf.parent_kind(), - Some(SyntaxKind::Equation) - | Some(SyntaxKind::Math) - | Some(SyntaxKind::MathFrac) - | Some(SyntaxKind::MathAttach) - ) { - return false; - } - - // Start of an interpolated identifier: "#|". - if ctx.leaf.kind() == SyntaxKind::Hashtag { - ctx.from = ctx.cursor; - code_completions(ctx, true); - return true; - } - - // Behind existing atom or identifier: "$a|$" or "$abc|$". - if matches!(ctx.leaf.kind(), SyntaxKind::Text | SyntaxKind::MathIdent) { - ctx.from = ctx.leaf.offset(); - math_completions(ctx); - return true; - } - - // Anywhere: "$|$". - if ctx.explicit { - ctx.from = ctx.cursor; - math_completions(ctx); - return true; - } - - false -} - -/// Add completions for math snippets. -#[rustfmt::skip] -fn math_completions(ctx: &mut CompletionContext) { - ctx.scope_completions(true, |_| true); - - ctx.snippet_completion( - "subscript", - "${x}_${2:2}", - "Sets something in subscript.", - ); - - ctx.snippet_completion( - "superscript", - "${x}^${2:2}", - "Sets something in superscript.", - ); - - ctx.snippet_completion( - "fraction", - "${x}/${y}", - "Inserts a fraction.", - ); -} - -/// Complete field accesses. -fn complete_field_accesses(ctx: &mut CompletionContext) -> bool { - // Behind an expression plus dot: "emoji.|". - if_chain! { - if ctx.leaf.kind() == SyntaxKind::Dot - || (ctx.leaf.kind() == SyntaxKind::Text - && ctx.leaf.text() == "."); - if ctx.leaf.range().end == ctx.cursor; - if let Some(prev) = ctx.leaf.prev_sibling(); - if prev.is::<ast::Expr>(); - if prev.parent_kind() != Some(SyntaxKind::Markup) || - prev.prev_sibling_kind() == Some(SyntaxKind::Hashtag); - if let Some(value) = analyze_expr(ctx.world, &prev).into_iter().next(); - then { - ctx.from = ctx.cursor; - field_access_completions(ctx, &value); - return true; - } - } - - // Behind a started field access: "emoji.fa|". - if_chain! { - if ctx.leaf.kind() == SyntaxKind::Ident; - if let Some(prev) = ctx.leaf.prev_sibling(); - if prev.kind() == SyntaxKind::Dot; - if let Some(prev_prev) = prev.prev_sibling(); - if prev_prev.is::<ast::Expr>(); - if let Some(value) = analyze_expr(ctx.world, &prev_prev).into_iter().next(); - then { - ctx.from = ctx.leaf.offset(); - field_access_completions(ctx, &value); - return true; - } - } - - false -} - -/// Add completions for all fields on a value. -fn field_access_completions(ctx: &mut CompletionContext, value: &Value) { - for &(method, args) in methods_on(value.type_name()) { - ctx.completions.push(Completion { - kind: CompletionKind::Func, - label: method.into(), - apply: Some(if args { - eco_format!("{method}(${{}})") - } else { - eco_format!("{method}()${{}}") - }), - detail: None, - }) - } - - match value { - Value::Symbol(symbol) => { - for modifier in symbol.modifiers() { - if let Ok(modified) = symbol.clone().modified(modifier) { - ctx.completions.push(Completion { - kind: CompletionKind::Symbol(modified.get()), - label: modifier.into(), - apply: None, - detail: None, - }); - } - } - } - Value::Content(content) => { - for (name, value) in content.fields() { - ctx.value_completion(Some(name.clone()), &value, false, None); - } - } - Value::Dict(dict) => { - for (name, value) in dict.iter() { - ctx.value_completion(Some(name.clone().into()), value, false, None); - } - } - Value::Module(module) => { - for (name, value) in module.scope().iter() { - ctx.value_completion(Some(name.clone()), value, true, None); - } - } - Value::Func(func) => { - if let Some(info) = func.info() { - // Consider all names from the function's scope. - for (name, value) in info.scope.iter() { - ctx.value_completion(Some(name.clone()), value, true, None); - } - } - } - _ => {} - } -} - -/// Complete imports. -fn complete_imports(ctx: &mut CompletionContext) -> bool { - // In an import path for a package: - // "#import "@|", - if_chain! { - if matches!( - ctx.leaf.parent_kind(), - Some(SyntaxKind::ModuleImport | SyntaxKind::ModuleInclude) - ); - if let Some(ast::Expr::Str(str)) = ctx.leaf.cast(); - if str.get().starts_with('@'); - then { - ctx.from = ctx.leaf.offset(); - ctx.package_completions(); - return true; - } - } - - // Behind an import list: - // "#import "path.typ": |", - // "#import "path.typ": a, b, |". - if_chain! { - if let Some(prev) = ctx.leaf.prev_sibling(); - if let Some(ast::Expr::Import(import)) = prev.cast(); - if let Some(ast::Imports::Items(items)) = import.imports(); - if let Some(source) = prev.children().find(|child| child.is::<ast::Expr>()); - if let Some(value) = analyze_expr(ctx.world, &source).into_iter().next(); - then { - ctx.from = ctx.cursor; - import_item_completions(ctx, &items, &value); - return true; - } - } - - // Behind a half-started identifier in an import list: - // "#import "path.typ": thi|", - if_chain! { - if ctx.leaf.kind() == SyntaxKind::Ident; - if let Some(parent) = ctx.leaf.parent(); - if parent.kind() == SyntaxKind::ImportItems; - if let Some(grand) = parent.parent(); - if let Some(ast::Expr::Import(import)) = grand.cast(); - if let Some(ast::Imports::Items(items)) = import.imports(); - if let Some(source) = grand.children().find(|child| child.is::<ast::Expr>()); - if let Some(value) = analyze_expr(ctx.world, &source).into_iter().next(); - then { - ctx.from = ctx.leaf.offset(); - import_item_completions(ctx, &items, &value); - return true; - } - } - - false -} - -/// Add completions for all exports of a module. -fn import_item_completions( - ctx: &mut CompletionContext, - existing: &[ast::Ident], - value: &Value, -) { - let module = match value { - Value::Str(path) => match analyze_import(ctx.world, ctx.source, path) { - Some(module) => module, - None => return, - }, - Value::Module(module) => module.clone(), - _ => return, - }; - - if existing.is_empty() { - ctx.snippet_completion("*", "*", "Import everything."); - } - - for (name, value) in module.scope().iter() { - if existing.iter().all(|ident| ident.as_str() != name) { - ctx.value_completion(Some(name.clone()), value, false, None); - } - } -} - -/// Complete set and show rules. -fn complete_rules(ctx: &mut CompletionContext) -> bool { - // We don't want to complete directly behind the keyword. - if !ctx.leaf.kind().is_trivia() { - return false; - } - - let Some(prev) = ctx.leaf.prev_leaf() else { return false }; - - // Behind the set keyword: "set |". - if matches!(prev.kind(), SyntaxKind::Set) { - ctx.from = ctx.cursor; - set_rule_completions(ctx); - return true; - } - - // Behind the show keyword: "show |". - if matches!(prev.kind(), SyntaxKind::Show) { - ctx.from = ctx.cursor; - show_rule_selector_completions(ctx); - return true; - } - - // Behind a half-completed show rule: "show strong: |". - if_chain! { - if let Some(prev) = ctx.leaf.prev_leaf(); - if matches!(prev.kind(), SyntaxKind::Colon); - if matches!(prev.parent_kind(), Some(SyntaxKind::ShowRule)); - then { - ctx.from = ctx.cursor; - show_rule_recipe_completions(ctx); - return true; - } - } - - false -} - -/// Add completions for all functions from the global scope. -fn set_rule_completions(ctx: &mut CompletionContext) { - ctx.scope_completions(true, |value| { - matches!( - value, - Value::Func(func) if func.info().map_or(false, |info| { - info.params.iter().any(|param| param.settable) - }), - ) - }); -} - -/// Add completions for selectors. -fn show_rule_selector_completions(ctx: &mut CompletionContext) { - ctx.scope_completions( - false, - |value| matches!(value, Value::Func(func) if func.element().is_some()), - ); - - ctx.enrich("", ": "); - - ctx.snippet_completion( - "text selector", - "\"${text}\": ${}", - "Replace occurrences of specific text.", - ); - - ctx.snippet_completion( - "regex selector", - "regex(\"${regex}\"): ${}", - "Replace matches of a regular expression.", - ); -} - -/// Add completions for recipes. -fn show_rule_recipe_completions(ctx: &mut CompletionContext) { - ctx.snippet_completion( - "replacement", - "[${content}]", - "Replace the selected element with content.", - ); - - ctx.snippet_completion( - "replacement (string)", - "\"${text}\"", - "Replace the selected element with a string of text.", - ); - - ctx.snippet_completion( - "transformation", - "element => [${content}]", - "Transform the element with a function.", - ); - - ctx.scope_completions(false, |value| matches!(value, Value::Func(_))); -} - -/// Complete call and set rule parameters. -fn complete_params(ctx: &mut CompletionContext) -> bool { - // Ensure that we are in a function call or set rule's argument list. - let (callee, set, args) = if_chain! { - if let Some(parent) = ctx.leaf.parent(); - if let Some(parent) = match parent.kind() { - SyntaxKind::Named => parent.parent(), - _ => Some(parent), - }; - if let Some(args) = parent.cast::<ast::Args>(); - if let Some(grand) = parent.parent(); - if let Some(expr) = grand.cast::<ast::Expr>(); - let set = matches!(expr, ast::Expr::Set(_)); - if let Some(ast::Expr::Ident(callee)) = match expr { - ast::Expr::FuncCall(call) => Some(call.callee()), - ast::Expr::Set(set) => Some(set.target()), - _ => None, - }; - then { - (callee, set, args) - } else { - return false; - } - }; - - // Find the piece of syntax that decides what we're completing. - let mut deciding = ctx.leaf.clone(); - while !matches!( - deciding.kind(), - SyntaxKind::LeftParen | SyntaxKind::Comma | SyntaxKind::Colon - ) { - let Some(prev) = deciding.prev_leaf() else { break }; - deciding = prev; - } - - // Parameter values: "func(param:|)", "func(param: |)". - if_chain! { - if deciding.kind() == SyntaxKind::Colon; - if let Some(prev) = deciding.prev_leaf(); - if let Some(param) = prev.cast::<ast::Ident>(); - then { - if let Some(next) = deciding.next_leaf() { - ctx.from = ctx.cursor.min(next.offset()); - } - - named_param_value_completions(ctx, &callee, ¶m); - return true; - } - } - - // Parameters: "func(|)", "func(hi|)", "func(12,|)". - if_chain! { - if matches!(deciding.kind(), SyntaxKind::LeftParen | SyntaxKind::Comma); - if deciding.kind() != SyntaxKind::Comma || deciding.range().end < ctx.cursor; - then { - if let Some(next) = deciding.next_leaf() { - ctx.from = ctx.cursor.min(next.offset()); - } - - // Exclude arguments which are already present. - let exclude: Vec<_> = args.items().filter_map(|arg| match arg { - ast::Arg::Named(named) => Some(named.name()), - _ => None, - }).collect(); - - param_completions(ctx, &callee, set, &exclude); - return true; - } - } - - false -} - -/// Add completions for the parameters of a function. -fn param_completions( - ctx: &mut CompletionContext, - callee: &ast::Ident, - set: bool, - exclude: &[ast::Ident], -) { - let info = if_chain! { - if let Some(Value::Func(func)) = ctx.global.get(callee); - if let Some(info) = func.info(); - then { info } - else { return; } - }; - - for param in &info.params { - if exclude.iter().any(|ident| ident.as_str() == param.name) { - continue; - } - - if set && !param.settable { - continue; - } - - if param.named { - ctx.completions.push(Completion { - kind: CompletionKind::Param, - label: param.name.into(), - apply: Some(eco_format!("{}: ${{}}", param.name)), - detail: Some(plain_docs_sentence(param.docs)), - }); - } - - if param.positional { - ctx.cast_completions(¶m.cast); - } - } - - if ctx.before.ends_with(',') { - ctx.enrich(" ", ""); - } -} - -/// Add completions for the values of a named function parameter. -fn named_param_value_completions( - ctx: &mut CompletionContext, - callee: &ast::Ident, - name: &str, -) { - let param = if_chain! { - if let Some(Value::Func(func)) = ctx.global.get(callee); - if let Some(info) = func.info(); - if let Some(param) = info.param(name); - if param.named; - then { param } - else { return; } - }; - - ctx.cast_completions(¶m.cast); - - if callee.as_str() == "text" && name == "font" { - ctx.font_completions(); - } - - if ctx.before.ends_with(':') { - ctx.enrich(" ", ""); - } -} - -/// Complete in code mode. -fn complete_code(ctx: &mut CompletionContext) -> bool { - if matches!( - ctx.leaf.parent_kind(), - None | Some(SyntaxKind::Markup) - | Some(SyntaxKind::Math) - | Some(SyntaxKind::MathFrac) - | Some(SyntaxKind::MathAttach) - | Some(SyntaxKind::MathRoot) - ) { - return false; - } - - // An existing identifier: "{ pa| }". - if ctx.leaf.kind() == SyntaxKind::Ident { - ctx.from = ctx.leaf.offset(); - code_completions(ctx, false); - return true; - } - - // Anywhere: "{ | }". - // But not within or after an expression. - if ctx.explicit - && (ctx.leaf.kind().is_trivia() - || matches!(ctx.leaf.kind(), SyntaxKind::LeftParen | SyntaxKind::LeftBrace)) - { - ctx.from = ctx.cursor; - code_completions(ctx, false); - return true; - } - - false -} - -/// Add completions for expression snippets. -#[rustfmt::skip] -fn code_completions(ctx: &mut CompletionContext, hashtag: bool) { - ctx.scope_completions(true, |value| !hashtag || { - matches!(value, Value::Symbol(_) | Value::Func(_) | Value::Module(_)) - }); - - ctx.snippet_completion( - "function call", - "${function}(${arguments})[${body}]", - "Evaluates a function.", - ); - - ctx.snippet_completion( - "code block", - "{ ${} }", - "Inserts a nested code block.", - ); - - ctx.snippet_completion( - "content block", - "[${content}]", - "Switches into markup mode.", - ); - - ctx.snippet_completion( - "set rule", - "set ${}", - "Sets style properties on an element.", - ); - - ctx.snippet_completion( - "show rule", - "show ${}", - "Redefines the look of an element.", - ); - - ctx.snippet_completion( - "show rule (everything)", - "show: ${}", - "Transforms everything that follows.", - ); - - ctx.snippet_completion( - "let binding", - "let ${name} = ${value}", - "Saves a value in a variable.", - ); - - ctx.snippet_completion( - "let binding (function)", - "let ${name}(${params}) = ${output}", - "Defines a function.", - ); - - ctx.snippet_completion( - "if conditional", - "if ${1 < 2} {\n\t${}\n}", - "Computes or inserts something conditionally.", - ); - - ctx.snippet_completion( - "if-else conditional", - "if ${1 < 2} {\n\t${}\n} else {\n\t${}\n}", - "Computes or inserts different things based on a condition.", - ); - - ctx.snippet_completion( - "while loop", - "while ${1 < 2} {\n\t${}\n}", - "Computes or inserts something while a condition is met.", - ); - - ctx.snippet_completion( - "for loop", - "for ${value} in ${(1, 2, 3)} {\n\t${}\n}", - "Computes or inserts something for each value in a collection.", - ); - - ctx.snippet_completion( - "for loop (with key)", - "for ${key}, ${value} in ${(a: 1, b: 2)} {\n\t${}\n}", - "Computes or inserts something for each key and value in a collection.", - ); - - ctx.snippet_completion( - "break", - "break", - "Exits early from a loop.", - ); - - ctx.snippet_completion( - "continue", - "continue", - "Continues with the next iteration of a loop.", - ); - - ctx.snippet_completion( - "return", - "return ${output}", - "Returns early from a function.", - ); - - ctx.snippet_completion( - "import (file)", - "import \"${file}.typ\": ${items}", - "Imports variables from another file.", - ); - - ctx.snippet_completion( - "import (package)", - "import \"@${}\": ${items}", - "Imports variables from another file.", - ); - - ctx.snippet_completion( - "include (file)", - "include \"${file}.typ\"", - "Includes content from another file.", - ); - - ctx.snippet_completion( - "include (package)", - "include \"@${}\"", - "Includes content from another file.", - ); - - ctx.snippet_completion( - "array", - "(${1, 2, 3})", - "Creates a sequence of values.", - ); - - ctx.snippet_completion( - "dictionary", - "(${a: 1, b: 2})", - "Creates a mapping from names to value.", - ); - - if !hashtag { - ctx.snippet_completion( - "function", - "(${params}) => ${output}", - "Creates an unnamed function.", - ); - } -} - -/// Context for autocompletion. -struct CompletionContext<'a> { - world: &'a (dyn World + 'static), - frames: &'a [Frame], - library: &'a Library, - source: &'a Source, - global: &'a Scope, - math: &'a Scope, - text: &'a str, - before: &'a str, - after: &'a str, - leaf: LinkedNode<'a>, - cursor: usize, - explicit: bool, - from: usize, - completions: Vec<Completion>, - seen_casts: HashSet<u128>, -} - -impl<'a> CompletionContext<'a> { - /// Create a new autocompletion context. - fn new( - world: &'a (dyn World + 'static), - frames: &'a [Frame], - source: &'a Source, - cursor: usize, - explicit: bool, - ) -> Option<Self> { - let text = source.text(); - let library = world.library(); - let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?; - Some(Self { - world, - frames, - library, - source, - global: library.global.scope(), - math: library.math.scope(), - text, - before: &text[..cursor], - after: &text[cursor..], - leaf, - cursor, - explicit, - from: cursor, - completions: vec![], - seen_casts: HashSet::new(), - }) - } - - /// Add a prefix and suffix to all applications. - fn enrich(&mut self, prefix: &str, suffix: &str) { - for Completion { label, apply, .. } in &mut self.completions { - let current = apply.as_ref().unwrap_or(label); - *apply = Some(eco_format!("{prefix}{current}{suffix}")); - } - } - - /// Add a snippet completion. - fn snippet_completion( - &mut self, - label: &'static str, - snippet: &'static str, - docs: &'static str, - ) { - self.completions.push(Completion { - kind: CompletionKind::Syntax, - label: label.into(), - apply: Some(snippet.into()), - detail: Some(docs.into()), - }); - } - - /// Add completions for all font families. - fn font_completions(&mut self) { - let equation = self.before[self.cursor.saturating_sub(25)..].contains("equation"); - for (family, iter) in self.world.book().families() { - let detail = summarize_font_family(iter); - if !equation || family.contains("Math") { - self.value_completion( - None, - &Value::Str(family.into()), - false, - Some(detail.as_str()), - ); - } - } - } - - /// Add completions for all available packages. - fn package_completions(&mut self) { - for (package, description) in self.world.packages() { - self.value_completion( - None, - &Value::Str(format_str!("{package}")), - false, - description.as_deref(), - ); - } - } - - /// Add completions for raw block tags. - fn raw_completions(&mut self) { - for (name, mut tags) in (self.library.items.raw_languages)() { - let lower = name.to_lowercase(); - if !tags.contains(&lower.as_str()) { - tags.push(lower.as_str()); - } - - tags.retain(|tag| is_ident(tag)); - if tags.is_empty() { - continue; - } - - self.completions.push(Completion { - kind: CompletionKind::Constant, - label: name.into(), - apply: Some(tags[0].into()), - detail: Some(separated_list(&tags, " or ").into()), - }); - } - } - - /// Add completions for all labels. - fn label_completions(&mut self) { - for (label, detail) in analyze_labels(self.world, self.frames).0 { - self.completions.push(Completion { - kind: CompletionKind::Constant, - label: label.0, - apply: None, - detail, - }); - } - } - - /// Add a completion for a specific value. - fn value_completion( - &mut self, - label: Option<EcoString>, - value: &Value, - parens: bool, - docs: Option<&str>, - ) { - let label = label.unwrap_or_else(|| value.repr().into()); - let mut apply = None; - - if label.starts_with('"') && self.after.starts_with('"') { - if let Some(trimmed) = label.strip_suffix('"') { - apply = Some(trimmed.into()); - } - } - - let detail = docs.map(Into::into).or_else(|| match value { - Value::Symbol(_) => None, - Value::Func(func) => func.info().map(|info| plain_docs_sentence(info.docs)), - v => { - let repr = v.repr(); - (repr.as_str() != label).then(|| repr.into()) - } - }); - - if parens && matches!(value, Value::Func(_)) { - apply = Some(eco_format!("{label}(${{}})")); - } - - self.completions.push(Completion { - kind: match value { - Value::Func(_) => CompletionKind::Func, - Value::Symbol(s) => CompletionKind::Symbol(s.get()), - _ => CompletionKind::Constant, - }, - label, - apply, - detail, - }); - } - - /// Add completions for a castable. - fn cast_completions(&mut self, cast: &'a CastInfo) { - // Prevent duplicate completions from appearing. - if !self.seen_casts.insert(crate::util::hash128(cast)) { - return; - } - - match cast { - CastInfo::Any => {} - CastInfo::Value(value, docs) => { - self.value_completion(None, value, true, Some(docs)); - } - CastInfo::Type("none") => self.snippet_completion("none", "none", "Nothing."), - CastInfo::Type("auto") => { - self.snippet_completion("auto", "auto", "A smart default."); - } - CastInfo::Type("boolean") => { - self.snippet_completion("false", "false", "No / Disabled."); - self.snippet_completion("true", "true", "Yes / Enabled."); - } - CastInfo::Type("color") => { - self.snippet_completion( - "luma()", - "luma(${v})", - "A custom grayscale color.", - ); - self.snippet_completion( - "rgb()", - "rgb(${r}, ${g}, ${b}, ${a})", - "A custom RGBA color.", - ); - self.snippet_completion( - "cmyk()", - "cmyk(${c}, ${m}, ${y}, ${k})", - "A custom CMYK color.", - ); - self.scope_completions(false, |value| value.type_name() == "color"); - } - CastInfo::Type("function") => { - self.snippet_completion( - "function", - "(${params}) => ${output}", - "A custom function.", - ); - } - CastInfo::Type(ty) => { - self.completions.push(Completion { - kind: CompletionKind::Syntax, - label: (*ty).into(), - apply: Some(eco_format!("${{{ty}}}")), - detail: Some(eco_format!("A value of type {ty}.")), - }); - self.scope_completions(false, |value| value.type_name() == *ty); - } - CastInfo::Union(union) => { - for info in union { - self.cast_completions(info); - } - } - } - } - - /// Add completions for definitions that are available at the cursor. - /// Filters the global/math scope with the given filter. - fn scope_completions(&mut self, parens: bool, filter: impl Fn(&Value) -> bool) { - let mut defined = BTreeSet::new(); - - let mut ancestor = Some(self.leaf.clone()); - while let Some(node) = &ancestor { - let mut sibling = Some(node.clone()); - while let Some(node) = &sibling { - if let Some(v) = node.cast::<ast::LetBinding>() { - for ident in v.kind().idents() { - defined.insert(ident.take()); - } - } - sibling = node.prev_sibling(); - } - - if let Some(parent) = node.parent() { - if let Some(v) = parent.cast::<ast::ForLoop>() { - if node.prev_sibling_kind() != Some(SyntaxKind::In) { - let pattern = v.pattern(); - for ident in pattern.idents() { - defined.insert(ident.take()); - } - } - } - - ancestor = Some(parent.clone()); - continue; - } - - break; - } - - let in_math = matches!( - self.leaf.parent_kind(), - Some(SyntaxKind::Equation) - | Some(SyntaxKind::Math) - | Some(SyntaxKind::MathFrac) - | Some(SyntaxKind::MathAttach) - ); - - let scope = if in_math { self.math } else { self.global }; - for (name, value) in scope.iter() { - if filter(value) && !defined.contains(name) { - self.value_completion(Some(name.clone()), value, parens, None); - } - } - - for name in defined { - if !name.is_empty() { - self.completions.push(Completion { - kind: CompletionKind::Constant, - label: name, - apply: None, - detail: None, - }); - } - } - } -} diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs deleted file mode 100644 index 2db636e3..00000000 --- a/src/ide/highlight.rs +++ /dev/null @@ -1,430 +0,0 @@ -use crate::syntax::{ast, LinkedNode, SyntaxKind, SyntaxNode}; - -/// A syntax highlighting tag. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Tag { - /// A line or block comment. - Comment, - /// Punctuation in code. - Punctuation, - /// An escape sequence or shorthand. - Escape, - /// Strong markup. - Strong, - /// Emphasized markup. - Emph, - /// A hyperlink. - Link, - /// Raw text. - Raw, - /// A label. - Label, - /// A reference to a label. - Ref, - /// A section heading. - Heading, - /// A marker of a list, enumeration, or term list. - ListMarker, - /// A term in a term list. - ListTerm, - /// The delimiters of an equation. - MathDelimiter, - /// An operator with special meaning in an equation. - MathOperator, - /// A keyword. - Keyword, - /// An operator in code. - Operator, - /// A numeric literal. - Number, - /// A string literal. - String, - /// A function or method name. - Function, - /// An interpolated variable in markup or math. - Interpolated, - /// A syntax error. - Error, -} - -impl Tag { - /// Return the recommended TextMate grammar scope for the given highlighting - /// tag. - pub fn tm_scope(&self) -> &'static str { - match self { - Self::Comment => "comment.typst", - Self::Punctuation => "punctuation.typst", - Self::Escape => "constant.character.escape.typst", - Self::Strong => "markup.bold.typst", - Self::Emph => "markup.italic.typst", - Self::Link => "markup.underline.link.typst", - Self::Raw => "markup.raw.typst", - Self::MathDelimiter => "punctuation.definition.math.typst", - Self::MathOperator => "keyword.operator.math.typst", - Self::Heading => "markup.heading.typst", - Self::ListMarker => "punctuation.definition.list.typst", - Self::ListTerm => "markup.list.term.typst", - Self::Label => "entity.name.label.typst", - Self::Ref => "markup.other.reference.typst", - Self::Keyword => "keyword.typst", - Self::Operator => "keyword.operator.typst", - Self::Number => "constant.numeric.typst", - Self::String => "string.quoted.double.typst", - Self::Function => "entity.name.function.typst", - Self::Interpolated => "meta.interpolation.typst", - Self::Error => "invalid.typst", - } - } - - /// The recommended CSS class for the highlighting tag. - pub fn css_class(self) -> &'static str { - match self { - Self::Comment => "typ-comment", - Self::Punctuation => "typ-punct", - Self::Escape => "typ-escape", - Self::Strong => "typ-strong", - Self::Emph => "typ-emph", - Self::Link => "typ-link", - Self::Raw => "typ-raw", - Self::Label => "typ-label", - Self::Ref => "typ-ref", - Self::Heading => "typ-heading", - Self::ListMarker => "typ-marker", - Self::ListTerm => "typ-term", - Self::MathDelimiter => "typ-math-delim", - Self::MathOperator => "typ-math-op", - Self::Keyword => "typ-key", - Self::Operator => "typ-op", - Self::Number => "typ-num", - Self::String => "typ-str", - Self::Function => "typ-func", - Self::Interpolated => "typ-pol", - Self::Error => "typ-error", - } - } -} - -/// Determine the highlight tag of a linked syntax node. -/// -/// Returns `None` if the node should not be highlighted. -pub fn highlight(node: &LinkedNode) -> Option<Tag> { - match node.kind() { - SyntaxKind::Markup - if node.parent_kind() == Some(SyntaxKind::TermItem) - && node.next_sibling_kind() == Some(SyntaxKind::Colon) => - { - Some(Tag::ListTerm) - } - SyntaxKind::Markup => None, - SyntaxKind::Text => None, - SyntaxKind::Space => None, - SyntaxKind::Linebreak => Some(Tag::Escape), - SyntaxKind::Parbreak => None, - SyntaxKind::Escape => Some(Tag::Escape), - SyntaxKind::Shorthand => Some(Tag::Escape), - SyntaxKind::SmartQuote => None, - SyntaxKind::Strong => Some(Tag::Strong), - SyntaxKind::Emph => Some(Tag::Emph), - SyntaxKind::Raw => Some(Tag::Raw), - SyntaxKind::Link => Some(Tag::Link), - SyntaxKind::Label => Some(Tag::Label), - SyntaxKind::Ref => Some(Tag::Ref), - SyntaxKind::RefMarker => None, - SyntaxKind::Heading => Some(Tag::Heading), - SyntaxKind::HeadingMarker => None, - SyntaxKind::ListItem => None, - SyntaxKind::ListMarker => Some(Tag::ListMarker), - SyntaxKind::EnumItem => None, - SyntaxKind::EnumMarker => Some(Tag::ListMarker), - SyntaxKind::TermItem => None, - SyntaxKind::TermMarker => Some(Tag::ListMarker), - SyntaxKind::Equation => None, - - SyntaxKind::Math => None, - SyntaxKind::MathIdent => highlight_ident(node), - SyntaxKind::MathAlignPoint => Some(Tag::MathOperator), - SyntaxKind::MathDelimited => None, - SyntaxKind::MathAttach => None, - SyntaxKind::MathFrac => None, - SyntaxKind::MathRoot => None, - - SyntaxKind::Hashtag => highlight_hashtag(node), - SyntaxKind::LeftBrace => Some(Tag::Punctuation), - SyntaxKind::RightBrace => Some(Tag::Punctuation), - SyntaxKind::LeftBracket => Some(Tag::Punctuation), - SyntaxKind::RightBracket => Some(Tag::Punctuation), - SyntaxKind::LeftParen => Some(Tag::Punctuation), - SyntaxKind::RightParen => Some(Tag::Punctuation), - SyntaxKind::Comma => Some(Tag::Punctuation), - SyntaxKind::Semicolon => Some(Tag::Punctuation), - SyntaxKind::Colon => Some(Tag::Punctuation), - SyntaxKind::Star => match node.parent_kind() { - Some(SyntaxKind::Strong) => None, - _ => Some(Tag::Operator), - }, - SyntaxKind::Underscore => match node.parent_kind() { - Some(SyntaxKind::MathAttach) => Some(Tag::MathOperator), - _ => None, - }, - SyntaxKind::Dollar => Some(Tag::MathDelimiter), - SyntaxKind::Plus => Some(Tag::Operator), - SyntaxKind::Minus => Some(Tag::Operator), - SyntaxKind::Slash => Some(match node.parent_kind() { - Some(SyntaxKind::MathFrac) => Tag::MathOperator, - _ => Tag::Operator, - }), - SyntaxKind::Hat => Some(Tag::MathOperator), - SyntaxKind::Dot => Some(Tag::Punctuation), - SyntaxKind::Eq => match node.parent_kind() { - Some(SyntaxKind::Heading) => None, - _ => Some(Tag::Operator), - }, - SyntaxKind::EqEq => Some(Tag::Operator), - SyntaxKind::ExclEq => Some(Tag::Operator), - SyntaxKind::Lt => Some(Tag::Operator), - SyntaxKind::LtEq => Some(Tag::Operator), - SyntaxKind::Gt => Some(Tag::Operator), - SyntaxKind::GtEq => Some(Tag::Operator), - SyntaxKind::PlusEq => Some(Tag::Operator), - SyntaxKind::HyphEq => Some(Tag::Operator), - SyntaxKind::StarEq => Some(Tag::Operator), - SyntaxKind::SlashEq => Some(Tag::Operator), - SyntaxKind::Dots => Some(Tag::Operator), - SyntaxKind::Arrow => Some(Tag::Operator), - SyntaxKind::Root => Some(Tag::MathOperator), - - SyntaxKind::Not => Some(Tag::Keyword), - SyntaxKind::And => Some(Tag::Keyword), - SyntaxKind::Or => Some(Tag::Keyword), - SyntaxKind::None => Some(Tag::Keyword), - SyntaxKind::Auto => Some(Tag::Keyword), - SyntaxKind::Let => Some(Tag::Keyword), - SyntaxKind::Set => Some(Tag::Keyword), - SyntaxKind::Show => Some(Tag::Keyword), - SyntaxKind::If => Some(Tag::Keyword), - SyntaxKind::Else => Some(Tag::Keyword), - SyntaxKind::For => Some(Tag::Keyword), - SyntaxKind::In => Some(Tag::Keyword), - SyntaxKind::While => Some(Tag::Keyword), - SyntaxKind::Break => Some(Tag::Keyword), - SyntaxKind::Continue => Some(Tag::Keyword), - SyntaxKind::Return => Some(Tag::Keyword), - SyntaxKind::Import => Some(Tag::Keyword), - SyntaxKind::Include => Some(Tag::Keyword), - SyntaxKind::As => Some(Tag::Keyword), - - SyntaxKind::Code => None, - SyntaxKind::Ident => highlight_ident(node), - SyntaxKind::Bool => Some(Tag::Keyword), - SyntaxKind::Int => Some(Tag::Number), - SyntaxKind::Float => Some(Tag::Number), - SyntaxKind::Numeric => Some(Tag::Number), - SyntaxKind::Str => Some(Tag::String), - SyntaxKind::CodeBlock => None, - SyntaxKind::ContentBlock => None, - SyntaxKind::Parenthesized => None, - SyntaxKind::Array => None, - SyntaxKind::Dict => None, - SyntaxKind::Named => None, - SyntaxKind::Keyed => None, - SyntaxKind::Unary => None, - SyntaxKind::Binary => None, - SyntaxKind::FieldAccess => None, - SyntaxKind::FuncCall => None, - SyntaxKind::Args => None, - SyntaxKind::Spread => None, - SyntaxKind::Closure => None, - SyntaxKind::Params => None, - SyntaxKind::LetBinding => None, - SyntaxKind::SetRule => None, - SyntaxKind::ShowRule => None, - SyntaxKind::Conditional => None, - SyntaxKind::WhileLoop => None, - SyntaxKind::ForLoop => None, - SyntaxKind::ModuleImport => None, - SyntaxKind::ImportItems => None, - SyntaxKind::ModuleInclude => None, - SyntaxKind::LoopBreak => None, - SyntaxKind::LoopContinue => None, - SyntaxKind::FuncReturn => None, - SyntaxKind::Destructuring => None, - SyntaxKind::DestructAssignment => None, - - SyntaxKind::LineComment => Some(Tag::Comment), - SyntaxKind::BlockComment => Some(Tag::Comment), - SyntaxKind::Error => Some(Tag::Error), - SyntaxKind::Eof => None, - } -} - -/// Highlight an identifier based on context. -fn highlight_ident(node: &LinkedNode) -> Option<Tag> { - // Are we directly before an argument list? - let next_leaf = node.next_leaf(); - if let Some(next) = &next_leaf { - if node.range().end == next.offset() - && ((next.kind() == SyntaxKind::LeftParen - && matches!( - next.parent_kind(), - Some(SyntaxKind::Args | SyntaxKind::Params) - )) - || (next.kind() == SyntaxKind::LeftBracket - && next.parent_kind() == Some(SyntaxKind::ContentBlock))) - { - return Some(Tag::Function); - } - } - - // Are we in math? - if node.kind() == SyntaxKind::MathIdent { - return Some(Tag::Interpolated); - } - - // Find the first non-field access ancestor. - let mut ancestor = node; - while ancestor.parent_kind() == Some(SyntaxKind::FieldAccess) { - ancestor = ancestor.parent()?; - } - - // Are we directly before or behind a show rule colon? - if ancestor.parent_kind() == Some(SyntaxKind::ShowRule) - && (next_leaf.map(|leaf| leaf.kind()) == Some(SyntaxKind::Colon) - || node.prev_leaf().map(|leaf| leaf.kind()) == Some(SyntaxKind::Colon)) - { - return Some(Tag::Function); - } - - // Are we (or an ancestor field access) directly after a hashtag. - if ancestor.prev_leaf().map(|leaf| leaf.kind()) == Some(SyntaxKind::Hashtag) { - return Some(Tag::Interpolated); - } - - // Are we behind a dot, that is behind another identifier? - let prev = node.prev_leaf()?; - if prev.kind() == SyntaxKind::Dot { - let prev_prev = prev.prev_leaf()?; - if is_ident(&prev_prev) { - return highlight_ident(&prev_prev); - } - } - - None -} - -/// Highlight a hashtag based on context. -fn highlight_hashtag(node: &LinkedNode) -> Option<Tag> { - let next = node.next_sibling()?; - let expr = next.cast::<ast::Expr>()?; - if !expr.hashtag() { - return None; - } - highlight(&next.leftmost_leaf()?) -} - -/// Whether the node is one of the two identifier nodes. -fn is_ident(node: &LinkedNode) -> bool { - matches!(node.kind(), SyntaxKind::Ident | SyntaxKind::MathIdent) -} - -/// Highlight a node to an HTML `code` element. -/// -/// This uses these [CSS classes for categories](Tag::css_class). -pub fn highlight_html(root: &SyntaxNode) -> String { - let mut buf = String::from("<code>"); - let node = LinkedNode::new(root); - highlight_html_impl(&mut buf, &node); - buf.push_str("</code>"); - buf -} - -/// Highlight one source node, emitting HTML. -fn highlight_html_impl(html: &mut String, node: &LinkedNode) { - let mut span = false; - if let Some(tag) = highlight(node) { - if tag != Tag::Error { - span = true; - html.push_str("<span class=\""); - html.push_str(tag.css_class()); - html.push_str("\">"); - } - } - - let text = node.text(); - if !text.is_empty() { - for c in text.chars() { - match c { - '<' => html.push_str("<"), - '>' => html.push_str(">"), - '&' => html.push_str("&"), - '\'' => html.push_str("'"), - '"' => html.push_str("""), - _ => html.push(c), - } - } - } else { - for child in node.children() { - highlight_html_impl(html, &child); - } - } - - if span { - html.push_str("</span>"); - } -} - -#[cfg(test)] -mod tests { - use std::ops::Range; - - use super::*; - use crate::syntax::Source; - - #[test] - fn test_highlighting() { - use Tag::*; - - #[track_caller] - fn test(text: &str, goal: &[(Range<usize>, Tag)]) { - let mut vec = vec![]; - let source = Source::detached(text); - highlight_tree(&mut vec, &LinkedNode::new(source.root())); - assert_eq!(vec, goal); - } - - fn highlight_tree(tags: &mut Vec<(Range<usize>, Tag)>, node: &LinkedNode) { - if let Some(tag) = highlight(node) { - tags.push((node.range(), tag)); - } - - for child in node.children() { - highlight_tree(tags, &child); - } - } - - test("= *AB*", &[(0..6, Heading), (2..6, Strong)]); - - test( - "#f(x + 1)", - &[ - (0..1, Function), - (1..2, Function), - (2..3, Punctuation), - (5..6, Operator), - (7..8, Number), - (8..9, Punctuation), - ], - ); - - test( - "#let f(x) = x", - &[ - (0..1, Keyword), - (1..4, Keyword), - (5..6, Function), - (6..7, Punctuation), - (8..9, Punctuation), - (10..11, Operator), - ], - ); - } -} diff --git a/src/ide/jump.rs b/src/ide/jump.rs deleted file mode 100644 index 14a82e26..00000000 --- a/src/ide/jump.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::num::NonZeroUsize; - -use ecow::EcoString; - -use crate::doc::{Destination, Frame, FrameItem, Meta, Position}; -use crate::file::FileId; -use crate::geom::{Geometry, Point, Size}; -use crate::model::Introspector; -use crate::syntax::{LinkedNode, Source, Span, SyntaxKind}; -use crate::World; - -/// Where to [jump](jump_from_click) to. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum Jump { - /// Jump to a position in a source file. - Source(FileId, usize), - /// Jump to an external URL. - Url(EcoString), - /// Jump to a point on a page. - Position(Position), -} - -impl Jump { - fn from_span(world: &dyn World, span: Span) -> Option<Self> { - let source = world.source(span.id()).ok()?; - let node = source.find(span)?; - Some(Self::Source(span.id(), node.offset())) - } -} - -/// Determine where to jump to based on a click in a frame. -pub fn jump_from_click( - world: &dyn World, - frames: &[Frame], - frame: &Frame, - click: Point, -) -> Option<Jump> { - let mut introspector = None; - - // Try to find a link first. - for (pos, item) in frame.items() { - if let FrameItem::Meta(Meta::Link(dest), size) = item { - if is_in_rect(*pos, *size, click) { - return Some(match dest { - Destination::Url(url) => Jump::Url(url.clone()), - Destination::Position(pos) => Jump::Position(*pos), - Destination::Location(loc) => Jump::Position( - introspector - .get_or_insert_with(|| Introspector::new(frames)) - .position(*loc), - ), - }); - } - } - } - - // If there's no link, search for a jump target. - for (mut pos, item) in frame.items().rev() { - match item { - FrameItem::Group(group) => { - // TODO: Handle transformation. - if let Some(span) = - jump_from_click(world, frames, &group.frame, click - pos) - { - return Some(span); - } - } - - FrameItem::Text(text) => { - for glyph in &text.glyphs { - let (span, span_offset) = glyph.span; - if span.is_detached() { - continue; - } - - let width = glyph.x_advance.at(text.size); - if is_in_rect( - Point::new(pos.x, pos.y - text.size), - Size::new(width, text.size), - click, - ) { - let source = world.source(span.id()).ok()?; - let node = source.find(span)?; - let pos = if node.kind() == SyntaxKind::Text { - let range = node.range(); - let mut offset = range.start + usize::from(span_offset); - if (click.x - pos.x) > width / 2.0 { - offset += glyph.range().len(); - } - offset.min(range.end) - } else { - node.offset() - }; - return Some(Jump::Source(source.id(), pos)); - } - - pos.x += width; - } - } - - FrameItem::Shape(shape, span) => { - let Geometry::Rect(size) = shape.geometry else { continue }; - if is_in_rect(pos, size, click) { - return Jump::from_span(world, *span); - } - } - - FrameItem::Image(_, size, span) if is_in_rect(pos, *size, click) => { - return Jump::from_span(world, *span); - } - - _ => {} - } - } - - None -} - -/// Find the output location in the document for a cursor position. -pub fn jump_from_cursor( - frames: &[Frame], - source: &Source, - cursor: usize, -) -> Option<Position> { - let node = LinkedNode::new(source.root()).leaf_at(cursor)?; - if node.kind() != SyntaxKind::Text { - return None; - } - - let span = node.span(); - for (i, frame) in frames.iter().enumerate() { - if let Some(pos) = find_in_frame(frame, span) { - return Some(Position { - page: NonZeroUsize::new(i + 1).unwrap(), - point: pos, - }); - } - } - - None -} - -/// Find the position of a span in a frame. -fn find_in_frame(frame: &Frame, span: Span) -> Option<Point> { - for (mut pos, item) in frame.items() { - if let FrameItem::Group(group) = item { - // TODO: Handle transformation. - if let Some(point) = find_in_frame(&group.frame, span) { - return Some(point + pos); - } - } - - if let FrameItem::Text(text) = item { - for glyph in &text.glyphs { - if glyph.span.0 == span { - return Some(pos); - } - pos.x += glyph.x_advance.at(text.size); - } - } - } - - None -} - -/// Whether a rectangle with the given size at the given position contains the -/// click position. -fn is_in_rect(pos: Point, size: Size, click: Point) -> bool { - pos.x <= click.x - && pos.x + size.x >= click.x - && pos.y <= click.y - && pos.y + size.y >= click.y -} diff --git a/src/ide/mod.rs b/src/ide/mod.rs deleted file mode 100644 index 4b08b66b..00000000 --- a/src/ide/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Capabilities for IDE support. - -mod analyze; -mod complete; -mod highlight; -mod jump; -mod tooltip; - -pub use self::analyze::analyze_labels; -pub use self::complete::{autocomplete, Completion, CompletionKind}; -pub use self::highlight::{highlight, highlight_html, Tag}; -pub use self::jump::{jump_from_click, jump_from_cursor, Jump}; -pub use self::tooltip::{tooltip, Tooltip}; - -use std::fmt::Write; - -use ecow::{eco_format, EcoString}; - -use self::analyze::*; -use crate::font::{FontInfo, FontStyle}; - -/// Extract the first sentence of plain text of a piece of documentation. -/// -/// Removes Markdown formatting. -fn plain_docs_sentence(docs: &str) -> EcoString { - let mut s = unscanny::Scanner::new(docs); - let mut output = EcoString::new(); - let mut link = false; - while let Some(c) = s.eat() { - match c { - '`' => { - let mut raw = s.eat_until('`'); - if (raw.starts_with('{') && raw.ends_with('}')) - || (raw.starts_with('[') && raw.ends_with(']')) - { - raw = &raw[1..raw.len() - 1]; - } - - s.eat(); - output.push('`'); - output.push_str(raw); - output.push('`'); - } - '[' => link = true, - ']' if link => { - if s.eat_if('(') { - s.eat_until(')'); - s.eat(); - } else if s.eat_if('[') { - s.eat_until(']'); - s.eat(); - } - link = false - } - '*' | '_' => {} - '.' => { - output.push('.'); - break; - } - _ => output.push(c), - } - } - - output -} - -/// Create a short description of a font family. -fn summarize_font_family<'a>(variants: impl Iterator<Item = &'a FontInfo>) -> EcoString { - let mut infos: Vec<_> = variants.collect(); - infos.sort_by_key(|info| info.variant); - - let mut has_italic = false; - let mut min_weight = u16::MAX; - let mut max_weight = 0; - for info in &infos { - let weight = info.variant.weight.to_number(); - has_italic |= info.variant.style == FontStyle::Italic; - min_weight = min_weight.min(weight); - max_weight = min_weight.max(weight); - } - - let count = infos.len(); - let s = if count == 1 { "" } else { "s" }; - let mut detail = eco_format!("{count} variant{s}."); - - if min_weight == max_weight { - write!(detail, " Weight {min_weight}.").unwrap(); - } else { - write!(detail, " Weights {min_weight}–{max_weight}.").unwrap(); - } - - if has_italic { - detail.push_str(" Has italics."); - } - - detail -} diff --git a/src/ide/tooltip.rs b/src/ide/tooltip.rs deleted file mode 100644 index 35125e92..00000000 --- a/src/ide/tooltip.rs +++ /dev/null @@ -1,222 +0,0 @@ -use std::fmt::Write; - -use ecow::{eco_format, EcoString}; - -use if_chain::if_chain; - -use super::analyze::analyze_labels; -use super::{analyze_expr, plain_docs_sentence, summarize_font_family}; -use crate::doc::Frame; -use crate::eval::{CastInfo, Tracer, Value}; -use crate::geom::{round_2, Length, Numeric}; -use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; -use crate::util::pretty_comma_list; -use crate::World; - -/// Describe the item under the cursor. -pub fn tooltip( - world: &(dyn World + 'static), - frames: &[Frame], - source: &Source, - cursor: usize, -) -> Option<Tooltip> { - let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?; - if leaf.kind().is_trivia() { - return None; - } - - named_param_tooltip(world, &leaf) - .or_else(|| font_tooltip(world, &leaf)) - .or_else(|| ref_tooltip(world, frames, &leaf)) - .or_else(|| expr_tooltip(world, &leaf)) -} - -/// A hover tooltip. -#[derive(Debug, Clone)] -pub enum Tooltip { - /// A string of text. - Text(EcoString), - /// A string of Typst code. - Code(EcoString), -} - -/// Tooltip for a hovered expression. -fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<Tooltip> { - let mut ancestor = leaf; - while !ancestor.is::<ast::Expr>() { - ancestor = ancestor.parent()?; - } - - let expr = ancestor.cast::<ast::Expr>()?; - if !expr.hashtag() && !matches!(expr, ast::Expr::MathIdent(_)) { - return None; - } - - let values = analyze_expr(world, ancestor); - - if let [value] = values.as_slice() { - if let Some(docs) = value.docs() { - return Some(Tooltip::Text(plain_docs_sentence(docs))); - } - - if let &Value::Length(length) = value { - if let Some(tooltip) = length_tooltip(length) { - return Some(tooltip); - } - } - } - - if expr.is_literal() { - return None; - } - - let mut last = None; - let mut pieces: Vec<EcoString> = vec![]; - let mut iter = values.iter(); - for value in (&mut iter).take(Tracer::MAX - 1) { - 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(); - } - } - pieces.push(value.repr().into()); - last = Some((value, 1)); - } - - if let Some((_, count)) = last { - if count > 1 { - write!(pieces.last_mut().unwrap(), " (x{count})").unwrap(); - } - } - - if iter.next().is_some() { - pieces.push("...".into()); - } - - let tooltip = pretty_comma_list(&pieces, false); - (!tooltip.is_empty()).then(|| Tooltip::Code(tooltip.into())) -} - -/// Tooltip text for a hovered length. -fn length_tooltip(length: Length) -> Option<Tooltip> { - length.em.is_zero().then(|| { - Tooltip::Code(eco_format!( - "{}pt = {}mm = {}cm = {}in", - round_2(length.abs.to_pt()), - round_2(length.abs.to_mm()), - round_2(length.abs.to_cm()), - round_2(length.abs.to_inches()) - )) - }) -} - -/// Tooltip for a hovered reference. -fn ref_tooltip( - world: &(dyn World + 'static), - frames: &[Frame], - leaf: &LinkedNode, -) -> Option<Tooltip> { - if leaf.kind() != SyntaxKind::RefMarker { - return None; - } - - let target = leaf.text().trim_start_matches('@'); - for (label, detail) in analyze_labels(world, frames).0 { - if label.0 == target { - return Some(Tooltip::Text(detail?)); - } - } - - None -} - -/// Tooltips for components of a named parameter. -fn named_param_tooltip( - world: &(dyn World + 'static), - leaf: &LinkedNode, -) -> Option<Tooltip> { - let (info, named) = if_chain! { - // Ensure that we are in a named pair in the arguments to a function - // call or set rule. - if let Some(parent) = leaf.parent(); - if let Some(named) = parent.cast::<ast::Named>(); - if let Some(grand) = parent.parent(); - if matches!(grand.kind(), SyntaxKind::Args); - if let Some(grand_grand) = grand.parent(); - if let Some(expr) = grand_grand.cast::<ast::Expr>(); - if let Some(ast::Expr::Ident(callee)) = match expr { - ast::Expr::FuncCall(call) => Some(call.callee()), - ast::Expr::Set(set) => Some(set.target()), - _ => None, - }; - - // Find metadata about the function. - if let Some(Value::Func(func)) = world.library().global.scope().get(&callee); - if let Some(info) = func.info(); - then { (info, named) } - else { return None; } - }; - - // Hovering over the parameter name. - if_chain! { - if leaf.index() == 0; - if let Some(ident) = leaf.cast::<ast::Ident>(); - if let Some(param) = info.param(&ident); - then { - return Some(Tooltip::Text(plain_docs_sentence(param.docs))); - } - } - - // Hovering over a string parameter value. - if_chain! { - if let Some(string) = leaf.cast::<ast::Str>(); - if let Some(param) = info.param(&named.name()); - if let Some(docs) = find_string_doc(¶m.cast, &string.get()); - then { - return Some(Tooltip::Text(docs.into())); - } - } - - None -} - -/// Find documentation for a castable string. -fn find_string_doc(info: &CastInfo, string: &str) -> Option<&'static str> { - match info { - CastInfo::Value(Value::Str(s), docs) if s.as_str() == string => Some(docs), - CastInfo::Union(options) => { - options.iter().find_map(|option| find_string_doc(option, string)) - } - _ => None, - } -} - -/// Tooltip for font. -fn font_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> { - if_chain! { - // Ensure that we are on top of a string. - if let Some(string) = leaf.cast::<ast::Str>(); - let lower = string.get().to_lowercase(); - - // Ensure that we are in the arguments to the text function. - if let Some(parent) = leaf.parent(); - if let Some(named) = parent.cast::<ast::Named>(); - if named.name().as_str() == "font"; - - // Find the font family. - if let Some((_, iter)) = world - .book() - .families() - .find(|&(family, _)| family.to_lowercase().as_str() == lower.as_str()); - - then { - let detail = summarize_font_family(iter); - return Some(Tooltip::Text(detail)); - } - }; - - None -} diff --git a/src/image.rs b/src/image.rs deleted file mode 100644 index 3a245c14..00000000 --- a/src/image.rs +++ /dev/null @@ -1,449 +0,0 @@ -//! Image handling. - -use std::cell::RefCell; -use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter}; -use std::io; -use std::sync::Arc; - -use comemo::{Prehashed, Track, Tracked}; -use ecow::{EcoString, EcoVec}; -use image::codecs::gif::GifDecoder; -use image::codecs::jpeg::JpegDecoder; -use image::codecs::png::PngDecoder; -use image::io::Limits; -use image::{ImageDecoder, ImageResult}; -use usvg::{TreeParsing, TreeTextToPath}; - -use crate::diag::{format_xml_like_error, StrResult}; -use crate::font::Font; -use crate::geom::Axes; -use crate::util::Bytes; -use crate::World; - -/// A raster or vector image. -/// -/// Values of this type are cheap to clone and hash. -#[derive(Clone, Hash, Eq, PartialEq)] -pub struct Image(Arc<Prehashed<Repr>>); - -/// The internal representation. -#[derive(Hash)] -struct Repr { - /// The raw, undecoded image data. - data: Bytes, - /// The format of the encoded `buffer`. - format: ImageFormat, - /// The size of the image. - size: Axes<u32>, - /// A loader for fonts referenced by an image (currently, only applies to - /// SVG). - loader: PreparedLoader, - /// A text describing the image. - alt: Option<EcoString>, -} - -impl Image { - /// Create an image from a buffer and a format. - #[comemo::memoize] - pub fn new( - data: Bytes, - format: ImageFormat, - alt: Option<EcoString>, - ) -> StrResult<Self> { - let loader = PreparedLoader::default(); - let decoded = match format { - ImageFormat::Raster(format) => decode_raster(&data, format)?, - ImageFormat::Vector(VectorFormat::Svg) => { - decode_svg(&data, (&loader as &dyn SvgFontLoader).track())? - } - }; - - Ok(Self(Arc::new(Prehashed::new(Repr { - data, - format, - size: decoded.size(), - loader, - alt, - })))) - } - - /// Create a font-dependant image from a buffer and a format. - #[comemo::memoize] - pub fn with_fonts( - data: Bytes, - format: ImageFormat, - world: Tracked<dyn World + '_>, - fallback_family: Option<&str>, - alt: Option<EcoString>, - ) -> StrResult<Self> { - let loader = WorldLoader::new(world, fallback_family); - let decoded = match format { - ImageFormat::Raster(format) => decode_raster(&data, format)?, - ImageFormat::Vector(VectorFormat::Svg) => { - decode_svg(&data, (&loader as &dyn SvgFontLoader).track())? - } - }; - - Ok(Self(Arc::new(Prehashed::new(Repr { - data, - format, - size: decoded.size(), - loader: loader.into_prepared(), - alt, - })))) - } - - /// The raw image data. - pub fn data(&self) -> &Bytes { - &self.0.data - } - - /// The format of the image. - pub fn format(&self) -> ImageFormat { - self.0.format - } - - /// The size of the image in pixels. - pub fn size(&self) -> Axes<u32> { - self.0.size - } - - /// The width of the image in pixels. - pub fn width(&self) -> u32 { - self.size().x - } - - /// The height of the image in pixels. - pub fn height(&self) -> u32 { - self.size().y - } - - /// A text describing the image. - pub fn alt(&self) -> Option<&str> { - self.0.alt.as_deref() - } - - /// The decoded version of the image. - pub fn decoded(&self) -> Arc<DecodedImage> { - match self.format() { - ImageFormat::Raster(format) => decode_raster(self.data(), format), - ImageFormat::Vector(VectorFormat::Svg) => { - decode_svg(self.data(), (&self.0.loader as &dyn SvgFontLoader).track()) - } - } - .unwrap() - } -} - -impl Debug for Image { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("Image") - .field("format", &self.format()) - .field("width", &self.width()) - .field("height", &self.height()) - .field("alt", &self.alt()) - .finish() - } -} - -/// A raster or vector image format. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum ImageFormat { - /// A raster graphics format. - Raster(RasterFormat), - /// A vector graphics format. - Vector(VectorFormat), -} - -/// A raster graphics format. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum RasterFormat { - /// Raster format for illustrations and transparent graphics. - Png, - /// Lossy raster format suitable for photos. - Jpg, - /// Raster format that is typically used for short animated clips. - Gif, -} - -/// A vector graphics format. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum VectorFormat { - /// The vector graphics format of the web. - Svg, -} - -impl From<RasterFormat> for image::ImageFormat { - fn from(format: RasterFormat) -> Self { - match format { - RasterFormat::Png => image::ImageFormat::Png, - RasterFormat::Jpg => image::ImageFormat::Jpeg, - RasterFormat::Gif => image::ImageFormat::Gif, - } - } -} - -impl From<ttf_parser::RasterImageFormat> for RasterFormat { - fn from(format: ttf_parser::RasterImageFormat) -> Self { - match format { - ttf_parser::RasterImageFormat::PNG => RasterFormat::Png, - } - } -} - -impl From<ttf_parser::RasterImageFormat> for ImageFormat { - fn from(format: ttf_parser::RasterImageFormat) -> Self { - Self::Raster(format.into()) - } -} - -/// A decoded image. -pub enum DecodedImage { - /// A decoded pixel raster with its ICC profile. - Raster(image::DynamicImage, Option<IccProfile>, RasterFormat), - /// An decoded SVG tree. - Svg(usvg::Tree), -} - -impl DecodedImage { - /// The size of the image in pixels. - pub fn size(&self) -> Axes<u32> { - Axes::new(self.width(), self.height()) - } - - /// The width of the image in pixels. - pub fn width(&self) -> u32 { - match self { - Self::Raster(dynamic, _, _) => dynamic.width(), - Self::Svg(tree) => tree.size.width().ceil() as u32, - } - } - - /// The height of the image in pixels. - pub fn height(&self) -> u32 { - match self { - Self::Raster(dynamic, _, _) => dynamic.height(), - Self::Svg(tree) => tree.size.height().ceil() as u32, - } - } -} - -/// Raw data for of an ICC profile. -pub struct IccProfile(pub Vec<u8>); - -/// Decode a raster image. -#[comemo::memoize] -fn decode_raster(data: &Bytes, format: RasterFormat) -> StrResult<Arc<DecodedImage>> { - fn decode_with<'a, T: ImageDecoder<'a>>( - decoder: ImageResult<T>, - ) -> ImageResult<(image::DynamicImage, Option<IccProfile>)> { - let mut decoder = decoder?; - let icc = decoder.icc_profile().filter(|data| !data.is_empty()).map(IccProfile); - decoder.set_limits(Limits::default())?; - let dynamic = image::DynamicImage::from_decoder(decoder)?; - Ok((dynamic, icc)) - } - - let cursor = io::Cursor::new(data); - let (dynamic, icc) = match format { - RasterFormat::Jpg => decode_with(JpegDecoder::new(cursor)), - RasterFormat::Png => decode_with(PngDecoder::new(cursor)), - RasterFormat::Gif => decode_with(GifDecoder::new(cursor)), - } - .map_err(format_image_error)?; - - Ok(Arc::new(DecodedImage::Raster(dynamic, icc, format))) -} - -/// Decode an SVG image. -#[comemo::memoize] -fn decode_svg( - data: &Bytes, - loader: Tracked<dyn SvgFontLoader + '_>, -) -> StrResult<Arc<DecodedImage>> { - // Disable usvg's default to "Times New Roman". Instead, we default to - // the empty family and later, when we traverse the SVG, we check for - // empty and non-existing family names and replace them with the true - // fallback family. This way, we can memoize SVG decoding with and without - // fonts if the SVG does not contain text. - let opts = usvg::Options { font_family: String::new(), ..Default::default() }; - let mut tree = usvg::Tree::from_data(data, &opts).map_err(format_usvg_error)?; - if tree.has_text_nodes() { - let fontdb = load_svg_fonts(&tree, loader); - tree.convert_text(&fontdb); - } - Ok(Arc::new(DecodedImage::Svg(tree))) -} - -/// Discover and load the fonts referenced by an SVG. -fn load_svg_fonts( - tree: &usvg::Tree, - loader: Tracked<dyn SvgFontLoader + '_>, -) -> fontdb::Database { - let mut referenced = BTreeMap::<EcoString, bool>::new(); - let mut fontdb = fontdb::Database::new(); - let mut load = |family_cased: &str| { - let family = EcoString::from(family_cased.trim()).to_lowercase(); - if let Some(&success) = referenced.get(&family) { - return success; - } - - // We load all variants for the family, since we don't know which will - // be used. - let mut success = false; - for font in loader.load(&family) { - let source = Arc::new(font.data().clone()); - fontdb.load_font_source(fontdb::Source::Binary(source)); - success = true; - } - - referenced.insert(family, success); - success - }; - - // Load fallback family. - let fallback_cased = loader.fallback(); - if let Some(family_cased) = fallback_cased { - load(family_cased); - } - - // Find out which font families are referenced by the SVG. - traverse_svg(&tree.root, &mut |node| { - let usvg::NodeKind::Text(text) = &mut *node.borrow_mut() else { return }; - for chunk in &mut text.chunks { - for span in &mut chunk.spans { - for family_cased in &mut span.font.families { - if family_cased.is_empty() || !load(family_cased) { - let Some(fallback) = fallback_cased else { continue }; - *family_cased = fallback.into(); - } - } - } - } - }); - - fontdb -} - -/// Search for all font families referenced by an SVG. -fn traverse_svg<F>(node: &usvg::Node, f: &mut F) -where - F: FnMut(&usvg::Node), -{ - f(node); - for child in node.children() { - traverse_svg(&child, f); - } -} - -/// Interface for loading fonts for an SVG. -/// -/// Can be backed by a `WorldLoader` or a `PreparedLoader`. The first is used -/// when the image is initially decoded. It records all required fonts and -/// produces a `PreparedLoader` from it. This loader can then be used to -/// redecode the image with a cache hit from the initial decoding. This way, we -/// can cheaply access the decoded version of an image. -/// -/// The alternative would be to store the decoded image directly in the image, -/// but that would make `Image` not `Send` because `usvg::Tree` is not `Send`. -/// The current design also has the added benefit that large decoded images can -/// be evicted if they are not used anymore. -#[comemo::track] -trait SvgFontLoader { - /// Load all fonts for the given lowercased font family. - fn load(&self, lower_family: &str) -> EcoVec<Font>; - - /// The case-sensitive name of the fallback family. - fn fallback(&self) -> Option<&str>; -} - -/// Loads fonts for an SVG from a world -struct WorldLoader<'a> { - world: Tracked<'a, dyn World + 'a>, - seen: RefCell<BTreeMap<EcoString, EcoVec<Font>>>, - fallback_family_cased: Option<String>, -} - -impl<'a> WorldLoader<'a> { - fn new(world: Tracked<'a, dyn World + 'a>, fallback_family: Option<&str>) -> Self { - // Recover the non-lowercased version of the family because - // usvg is case sensitive. - let book = world.book(); - let fallback_family_cased = fallback_family - .and_then(|lowercase| book.select_family(lowercase).next()) - .and_then(|index| book.info(index)) - .map(|info| info.family.clone()); - - Self { - world, - fallback_family_cased, - seen: Default::default(), - } - } - - fn into_prepared(self) -> PreparedLoader { - PreparedLoader { - families: self.seen.into_inner(), - fallback_family_cased: self.fallback_family_cased, - } - } -} - -impl SvgFontLoader for WorldLoader<'_> { - fn load(&self, family: &str) -> EcoVec<Font> { - self.seen - .borrow_mut() - .entry(family.into()) - .or_insert_with(|| { - self.world - .book() - .select_family(family) - .filter_map(|id| self.world.font(id)) - .collect() - }) - .clone() - } - - fn fallback(&self) -> Option<&str> { - self.fallback_family_cased.as_deref() - } -} - -/// Loads fonts for an SVG from a prepared list. -#[derive(Default, Hash)] -struct PreparedLoader { - families: BTreeMap<EcoString, EcoVec<Font>>, - fallback_family_cased: Option<String>, -} - -impl SvgFontLoader for PreparedLoader { - fn load(&self, family: &str) -> EcoVec<Font> { - self.families.get(family).cloned().unwrap_or_default() - } - - fn fallback(&self) -> Option<&str> { - self.fallback_family_cased.as_deref() - } -} - -/// Format the user-facing raster graphic decoding error message. -fn format_image_error(error: image::ImageError) -> EcoString { - match error { - image::ImageError::Limits(_) => "file is too large".into(), - _ => "failed to decode image".into(), - } -} - -/// Format the user-facing SVG decoding error message. -fn format_usvg_error(error: usvg::Error) -> EcoString { - match error { - usvg::Error::NotAnUtf8Str => "file is not valid utf-8".into(), - usvg::Error::MalformedGZip => "file is not compressed correctly".into(), - usvg::Error::ElementsLimitReached => "file is too large".into(), - usvg::Error::InvalidSize => { - "failed to parse svg: width, height, or viewbox is invalid".into() - } - usvg::Error::ParsingFailed(error) => format_xml_like_error("svg", error), - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 8b3d1d3d..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! The compiler for the _Typst_ markup language. -//! -//! # Steps -//! - **Parsing:** -//! The compiler first transforms a plain string into an iterator of [tokens]. -//! This token stream is [parsed] into a [syntax tree]. The tree itself is -//! untyped, but the [AST] module provides a typed layer over it. -//! - **Evaluation:** -//! The next step is to [evaluate] the markup. This produces a [module], -//! consisting of a scope of values that were exported by the code and -//! [content], a hierarchical, styled representation of what was written in -//! the source file. The elements of the content tree are well structured and -//! order-independent and thus much better suited for further processing than -//! the raw markup. -//! - **Typesetting:** -//! Next, the content is [typeset] into a [document] containing one [frame] -//! per page with items at fixed positions. -//! - **Exporting:** -//! These frames can finally be exported into an output format (currently -//! supported are [PDF] and [raster images]). -//! -//! [tokens]: syntax::SyntaxKind -//! [parsed]: syntax::parse -//! [syntax tree]: syntax::SyntaxNode -//! [AST]: syntax::ast -//! [evaluate]: eval::eval -//! [module]: eval::Module -//! [content]: model::Content -//! [typeset]: model::typeset -//! [document]: doc::Document -//! [frame]: doc::Frame -//! [PDF]: export::pdf -//! [raster images]: export::render - -#![recursion_limit = "1000"] -#![allow(clippy::comparison_chain)] - -extern crate self as typst; - -#[macro_use] -pub mod util; -#[macro_use] -pub mod diag; -#[macro_use] -pub mod eval; -pub mod doc; -pub mod export; -pub mod file; -pub mod font; -pub mod geom; -pub mod ide; -pub mod image; -pub mod model; -pub mod syntax; - -use comemo::{Prehashed, Track, TrackedMut}; -use ecow::EcoString; - -use crate::diag::{FileResult, SourceResult}; -use crate::doc::Document; -use crate::eval::{Datetime, Library, Route, Tracer}; -use crate::file::{FileId, PackageSpec}; -use crate::font::{Font, FontBook}; -use crate::syntax::Source; -use crate::util::Bytes; - -/// Compile a source file into a fully layouted document. -#[tracing::instrument(skip(world))] -pub fn compile(world: &dyn World) -> SourceResult<Document> { - let route = Route::default(); - let mut tracer = Tracer::default(); - - // Call `track` just once to keep comemo's ID stable. - let world = world.track(); - let mut tracer = tracer.track_mut(); - - // Evaluate the source file into a module. - tracing::info!("Starting evaluation"); - let module = eval::eval( - world, - route.track(), - TrackedMut::reborrow_mut(&mut tracer), - &world.main(), - )?; - - // Typeset the module's contents. - model::typeset(world, tracer, &module.content()) -} - -/// The environment in which typesetting occurs. -/// -/// All loading functions (`main`, `source`, `file`, `font`) should perform -/// internal caching so that they are relatively cheap on repeated invocations -/// with the same argument. [`Source`], [`Bytes`], and [`Font`] are -/// all reference-counted and thus cheap to clone. -/// -/// The compiler doesn't do the caching itself because the world has much more -/// information on when something can change. For example, fonts typically don't -/// change and can thus even be cached across multiple compilations (for -/// long-running applications like `typst watch`). Source files on the other -/// hand can change and should thus be cleared after. Advanced clients like -/// language servers can also retain the source files and [edited](Source::edit) -/// them in-place to benefit from better incremental performance. -#[comemo::track] -pub trait World { - /// The standard library. - fn library(&self) -> &Prehashed<Library>; - - /// Metadata about all known fonts. - fn book(&self) -> &Prehashed<FontBook>; - - /// Access the main source file. - fn main(&self) -> Source; - - /// Try to access the specified source file. - /// - /// The returned `Source` file's [id](Source::id) does not have to match the - /// given `id`. Due to symlinks, two different file id's can point to the - /// same on-disk file. Implementors can deduplicate and return the same - /// `Source` if they want to, but do not have to. - fn source(&self, id: FileId) -> FileResult<Source>; - - /// Try to access the specified file. - fn file(&self, id: FileId) -> FileResult<Bytes>; - - /// Try to access the font with the given index in the font book. - fn font(&self, index: usize) -> Option<Font>; - - /// Get the current date. - /// - /// If no offset is specified, the local date should be chosen. Otherwise, - /// the UTC date should be chosen with the corresponding offset in hours. - /// - /// If this function returns `None`, Typst's `datetime` function will - /// return an error. - fn today(&self, offset: Option<i64>) -> Option<Datetime>; - - /// A list of all available packages and optionally descriptions for them. - /// - /// This function is optional to implement. It enhances the user experience - /// by enabling autocompletion for packages. Details about packages from the - /// `@preview` namespace are available from - /// `https://packages.typst.org/preview/index.json`. - fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] { - &[] - } -} diff --git a/src/model/content.rs b/src/model/content.rs deleted file mode 100644 index 015f8b76..00000000 --- a/src/model/content.rs +++ /dev/null @@ -1,614 +0,0 @@ -use std::any::TypeId; -use std::fmt::{self, Debug, Formatter, Write}; -use std::iter::Sum; -use std::ops::{Add, AddAssign}; - -use comemo::Prehashed; -use ecow::{eco_format, EcoString, EcoVec}; - -use super::{ - element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable, - Location, Recipe, Selector, Style, Styles, Synthesize, -}; -use crate::diag::{SourceResult, StrResult}; -use crate::doc::Meta; -use crate::eval::{Dict, FromValue, IntoValue, Str, Value, Vm}; -use crate::syntax::Span; -use crate::util::pretty_array_like; - -/// Composable representation of styled content. -#[derive(Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Content { - func: ElemFunc, - attrs: EcoVec<Attr>, -} - -/// Attributes that can be attached to content. -#[derive(Debug, Clone, PartialEq, Hash)] -enum Attr { - Span(Span), - Field(EcoString), - Value(Prehashed<Value>), - Child(Prehashed<Content>), - Styles(Styles), - Prepared, - Guard(Guard), - Location(Location), -} - -impl Content { - /// Create an empty element. - pub fn new(func: ElemFunc) -> Self { - Self { func, attrs: EcoVec::new() } - } - - /// Create empty content. - pub fn empty() -> Self { - Self::new(SequenceElem::func()) - } - - /// Create a new sequence element from multiples elements. - pub fn sequence(iter: impl IntoIterator<Item = Self>) -> Self { - let mut iter = iter.into_iter(); - let Some(first) = iter.next() else { return Self::empty() }; - let Some(second) = iter.next() else { return first }; - let mut content = Content::empty(); - content.attrs.push(Attr::Child(Prehashed::new(first))); - content.attrs.push(Attr::Child(Prehashed::new(second))); - content - .attrs - .extend(iter.map(|child| Attr::Child(Prehashed::new(child)))); - content - } - - /// The element function of the contained content. - pub fn func(&self) -> ElemFunc { - self.func - } - - /// Whether the content is an empty sequence. - pub fn is_empty(&self) -> bool { - self.is::<SequenceElem>() && self.attrs.is_empty() - } - - /// Whether the contained element is of type `T`. - pub fn is<T: Element>(&self) -> bool { - self.func == T::func() - } - - /// Cast to `T` if the contained element is of type `T`. - pub fn to<T: Element>(&self) -> Option<&T> { - T::unpack(self) - } - - /// Access the children if this is a sequence. - pub fn to_sequence(&self) -> Option<impl Iterator<Item = &Self>> { - if !self.is::<SequenceElem>() { - return None; - } - Some(self.attrs.iter().filter_map(Attr::child)) - } - - /// Access the child and styles. - pub fn to_styled(&self) -> Option<(&Content, &Styles)> { - if !self.is::<StyledElem>() { - return None; - } - let child = self.attrs.iter().find_map(Attr::child)?; - let styles = self.attrs.iter().find_map(Attr::styles)?; - Some((child, styles)) - } - - /// Whether the contained element has the given capability. - pub fn can<C>(&self) -> bool - where - C: ?Sized + 'static, - { - (self.func.0.vtable)(TypeId::of::<C>()).is_some() - } - - /// Whether the contained element has the given capability. - /// Where the capability is given by a `TypeId`. - pub fn can_type_id(&self, type_id: TypeId) -> bool { - (self.func.0.vtable)(type_id).is_some() - } - - /// Cast to a trait object if the contained element has the given - /// capability. - pub fn with<C>(&self) -> Option<&C> - where - C: ?Sized + 'static, - { - let vtable = (self.func.0.vtable)(TypeId::of::<C>())?; - let data = self as *const Self as *const (); - Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) - } - - /// Cast to a mutable trait object if the contained element has the given - /// capability. - pub fn with_mut<C>(&mut self) -> Option<&mut C> - where - C: ?Sized + 'static, - { - let vtable = (self.func.0.vtable)(TypeId::of::<C>())?; - let data = self as *mut Self as *mut (); - Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) }) - } - - /// The content's span. - pub fn span(&self) -> Span { - self.attrs.iter().find_map(Attr::span).unwrap_or(Span::detached()) - } - - /// Attach a span to the content if it doesn't already have one. - pub fn spanned(mut self, span: Span) -> Self { - if self.span().is_detached() { - self.attrs.push(Attr::Span(span)); - } - self - } - - /// Attach a field to the content. - pub fn with_field( - mut self, - name: impl Into<EcoString>, - value: impl IntoValue, - ) -> Self { - self.push_field(name, value); - self - } - - /// Attach a field to the content. - pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl IntoValue) { - let name = name.into(); - if let Some(i) = self.attrs.iter().position(|attr| match attr { - Attr::Field(field) => *field == name, - _ => false, - }) { - self.attrs.make_mut()[i + 1] = - Attr::Value(Prehashed::new(value.into_value())); - } else { - self.attrs.push(Attr::Field(name)); - self.attrs.push(Attr::Value(Prehashed::new(value.into_value()))); - } - } - - /// Access a field on the content. - pub fn field(&self, name: &str) -> Option<Value> { - if let (Some(iter), "children") = (self.to_sequence(), name) { - Some(Value::Array(iter.cloned().map(Value::Content).collect())) - } else if let (Some((child, _)), "child") = (self.to_styled(), name) { - Some(Value::Content(child.clone())) - } else { - self.field_ref(name).cloned() - } - } - - /// Access a field on the content by reference. - /// - /// Does not include synthesized fields for sequence and styled elements. - pub fn field_ref(&self, name: &str) -> Option<&Value> { - self.fields_ref() - .find(|&(field, _)| field == name) - .map(|(_, value)| value) - } - - /// Iter over all fields on the content. - /// - /// Does not include synthesized fields for sequence and styled elements. - pub fn fields(&self) -> impl Iterator<Item = (&EcoString, Value)> { - static CHILD: EcoString = EcoString::inline("child"); - static CHILDREN: EcoString = EcoString::inline("children"); - - let option = if let Some(iter) = self.to_sequence() { - Some((&CHILDREN, Value::Array(iter.cloned().map(Value::Content).collect()))) - } else if let Some((child, _)) = self.to_styled() { - Some((&CHILD, Value::Content(child.clone()))) - } else { - None - }; - - self.fields_ref() - .map(|(name, value)| (name, value.clone())) - .chain(option) - } - - /// Iter over all fields on the content. - /// - /// Does not include synthesized fields for sequence and styled elements. - pub fn fields_ref(&self) -> impl Iterator<Item = (&EcoString, &Value)> { - let mut iter = self.attrs.iter(); - std::iter::from_fn(move || { - let field = iter.find_map(Attr::field)?; - let value = iter.next()?.value()?; - Some((field, value)) - }) - } - - /// Try to access a field on the content as a specified type. - pub fn cast_field<T: FromValue>(&self, name: &str) -> Option<T> { - match self.field(name) { - Some(value) => value.cast().ok(), - None => None, - } - } - - /// Expect a field on the content to exist as a specified type. - #[track_caller] - pub fn expect_field<T: FromValue>(&self, name: &str) -> T { - self.field(name).unwrap().cast().unwrap() - } - - /// Whether the content has the specified field. - pub fn has(&self, field: &str) -> bool { - self.field(field).is_some() - } - - /// Borrow the value of the given field. - pub fn at(&self, field: &str, default: Option<Value>) -> StrResult<Value> { - self.field(field) - .or(default) - .ok_or_else(|| missing_field_no_default(field)) - } - - /// Return the fields of the content as a dict. - pub fn dict(&self) -> Dict { - self.fields() - .map(|(key, value)| (key.to_owned().into(), value)) - .collect() - } - - /// The content's label. - pub fn label(&self) -> Option<&Label> { - match self.field_ref("label")? { - Value::Label(label) => Some(label), - _ => None, - } - } - - /// 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(mut self, style: impl Into<Style>) -> Self { - if self.is::<StyledElem>() { - let prev = - self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap(); - prev.apply_one(style.into()); - self - } else { - self.styled_with_map(style.into().into()) - } - } - - /// Style this content with a full style map. - pub fn styled_with_map(mut self, styles: Styles) -> Self { - if styles.is_empty() { - return self; - } - - if self.is::<StyledElem>() { - let prev = - self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap(); - prev.apply(styles); - self - } else { - let mut content = Content::new(StyledElem::func()); - content.attrs.push(Attr::Child(Prehashed::new(self))); - content.attrs.push(Attr::Styles(styles)); - content - } - } - - /// Style this content with a recipe, eagerly applying it if possible. - pub fn styled_with_recipe(self, vm: &mut Vm, recipe: Recipe) -> SourceResult<Self> { - if recipe.selector.is_none() { - recipe.apply_vm(vm, self) - } else { - Ok(self.styled(recipe)) - } - } - - /// Repeat this content `count` times. - pub fn repeat(&self, count: usize) -> Self { - Self::sequence(vec![self.clone(); count]) - } - - /// Disable a show rule recipe. - pub fn guarded(mut self, guard: Guard) -> Self { - self.attrs.push(Attr::Guard(guard)); - self - } - - /// Check whether a show rule recipe is disabled. - pub fn is_guarded(&self, guard: Guard) -> bool { - self.attrs.contains(&Attr::Guard(guard)) - } - - /// Whether no show rule was executed for this content so far. - pub fn is_pristine(&self) -> bool { - !self.attrs.iter().any(|modifier| matches!(modifier, Attr::Guard(_))) - } - - /// Whether this content has already been prepared. - pub fn is_prepared(&self) -> bool { - self.attrs.contains(&Attr::Prepared) - } - - /// Mark this content as prepared. - pub fn mark_prepared(&mut self) { - self.attrs.push(Attr::Prepared); - } - - /// Whether the content needs to be realized specially. - pub fn needs_preparation(&self) -> bool { - (self.can::<dyn Locatable>() - || self.can::<dyn Synthesize>() - || self.label().is_some()) - && !self.is_prepared() - } - - /// This content's location in the document flow. - pub fn location(&self) -> Option<Location> { - self.attrs.iter().find_map(|modifier| match modifier { - Attr::Location(location) => Some(*location), - _ => None, - }) - } - - /// Attach a location to this content. - pub fn set_location(&mut self, location: Location) { - self.attrs.push(Attr::Location(location)); - } - - /// Queries the content tree for all elements that match the given selector. - /// - /// Elements produced in `show` rules will not be included in the results. - #[tracing::instrument(skip_all)] - pub fn query(&self, selector: Selector) -> Vec<&Content> { - let mut results = Vec::new(); - self.traverse(&mut |element| { - if selector.matches(element) { - results.push(element); - } - }); - results - } - - /// Queries the content tree for the first element that match the given - /// selector. - /// - /// Elements produced in `show` rules will not be included in the results. - #[tracing::instrument(skip_all)] - pub fn query_first(&self, selector: Selector) -> Option<&Content> { - let mut result = None; - self.traverse(&mut |element| { - if result.is_none() && selector.matches(element) { - result = Some(element); - } - }); - result - } - - /// Extracts the plain text of this content. - pub fn plain_text(&self) -> EcoString { - let mut text = EcoString::new(); - self.traverse(&mut |element| { - if let Some(textable) = element.with::<dyn PlainText>() { - textable.plain_text(&mut text); - } - }); - text - } - - /// Traverse this content. - fn traverse<'a, F>(&'a self, f: &mut F) - where - F: FnMut(&'a Content), - { - f(self); - - for attr in &self.attrs { - match attr { - Attr::Child(child) => child.traverse(f), - Attr::Value(value) => walk_value(value, f), - _ => {} - } - } - - /// Walks a given value to find any content that matches the selector. - fn walk_value<'a, F>(value: &'a Value, f: &mut F) - where - F: FnMut(&'a Content), - { - match value { - Value::Content(content) => content.traverse(f), - Value::Array(array) => { - for value in array { - walk_value(value, f); - } - } - _ => {} - } - } - } -} - -impl Debug for Content { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let name = self.func.name(); - if let Some(text) = item!(text_str)(self) { - f.write_char('[')?; - f.write_str(&text)?; - f.write_char(']')?; - return Ok(()); - } else if name == "space" { - return f.write_str("[ ]"); - } - - let mut pieces: Vec<_> = self - .fields() - .map(|(name, value)| eco_format!("{name}: {value:?}")) - .collect(); - - if self.is::<StyledElem>() { - pieces.push(EcoString::from("..")); - } - - f.write_str(name)?; - f.write_str(&pretty_array_like(&pieces, false)) - } -} - -impl Default for Content { - fn default() -> Self { - Self::empty() - } -} - -impl PartialEq for Content { - fn eq(&self, other: &Self) -> bool { - if let (Some(left), Some(right)) = (self.to_sequence(), other.to_sequence()) { - left.eq(right) - } else if let (Some(left), Some(right)) = (self.to_styled(), other.to_styled()) { - left == right - } else { - self.func == other.func && self.fields_ref().eq(other.fields_ref()) - } - } -} - -impl Add for Content { - type Output = Self; - - fn add(self, mut rhs: Self) -> Self::Output { - let mut lhs = self; - match (lhs.is::<SequenceElem>(), rhs.is::<SequenceElem>()) { - (true, true) => { - lhs.attrs.extend(rhs.attrs); - lhs - } - (true, false) => { - lhs.attrs.push(Attr::Child(Prehashed::new(rhs))); - lhs - } - (false, true) => { - rhs.attrs.insert(0, Attr::Child(Prehashed::new(lhs))); - rhs - } - (false, false) => Self::sequence([lhs, rhs]), - } - } -} - -impl AddAssign for Content { - fn add_assign(&mut self, rhs: Self) { - *self = std::mem::take(self) + rhs; - } -} - -impl Sum for Content { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self::sequence(iter) - } -} - -impl Attr { - fn child(&self) -> Option<&Content> { - match self { - Self::Child(child) => Some(child), - _ => None, - } - } - - fn styles(&self) -> Option<&Styles> { - match self { - Self::Styles(styles) => Some(styles), - _ => None, - } - } - - fn styles_mut(&mut self) -> Option<&mut Styles> { - match self { - Self::Styles(styles) => Some(styles), - _ => None, - } - } - - fn field(&self) -> Option<&EcoString> { - match self { - Self::Field(field) => Some(field), - _ => None, - } - } - - fn value(&self) -> Option<&Value> { - match self { - Self::Value(value) => Some(value), - _ => None, - } - } - - fn span(&self) -> Option<Span> { - match self { - Self::Span(span) => Some(*span), - _ => None, - } - } -} - -/// Display: Sequence -/// Category: special -#[element] -struct SequenceElem {} - -/// Display: Sequence -/// Category: special -#[element] -struct StyledElem {} - -/// Hosts metadata and ensures metadata is produced even for empty elements. -/// -/// Display: Meta -/// Category: special -#[element(Behave)] -pub struct MetaElem { - /// Metadata that should be attached to all elements affected by this style - /// property. - #[fold] - pub data: Vec<Meta>, -} - -impl Behave for MetaElem { - 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 - } -} - -/// Tries to extract the plain-text representation of the element. -pub trait PlainText { - /// Write this element's plain text into the given buffer. - fn plain_text(&self, text: &mut EcoString); -} - -/// The missing key access error message when no default value was given. -#[cold] -fn missing_field_no_default(key: &str) -> EcoString { - eco_format!( - "content does not contain field {:?} and \ - no default value was specified", - Str::from(key) - ) -} diff --git a/src/model/element.rs b/src/model/element.rs deleted file mode 100644 index c673ee41..00000000 --- a/src/model/element.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::any::TypeId; -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; - -use once_cell::sync::Lazy; - -use super::{Content, Selector, Styles}; -use crate::diag::SourceResult; -use crate::eval::{cast, Args, Dict, Func, FuncInfo, Value, Vm}; - -/// A document element. -pub trait Element: Construct + Set + Sized + 'static { - /// Pack the element into type-erased content. - fn pack(self) -> Content; - - /// Extract this element from type-erased content. - fn unpack(content: &Content) -> Option<&Self>; - - /// The element's function. - fn func() -> ElemFunc; -} - -/// An element's constructor function. -pub trait Construct { - /// Construct an element from the arguments. - /// - /// This is passed only the arguments that remain after execution of the - /// element's set rule. - fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>; -} - -/// An element's set rule. -pub trait Set { - /// Parse relevant arguments into style properties for this element. - fn set(args: &mut Args) -> SourceResult<Styles>; -} - -/// An element's function. -#[derive(Copy, Clone)] -pub struct ElemFunc(pub(super) &'static NativeElemFunc); - -impl ElemFunc { - /// The function's name. - pub fn name(self) -> &'static str { - self.0.name - } - - /// Apply the given arguments to the function. - pub fn with(self, args: Args) -> Func { - Func::from(self).with(args) - } - - /// Extract details about the function. - pub fn info(&self) -> &'static FuncInfo { - &self.0.info - } - - /// Construct an element. - pub fn construct(self, vm: &mut Vm, args: &mut Args) -> SourceResult<Content> { - (self.0.construct)(vm, args) - } - - /// Whether the contained element has the given capability. - pub fn can<C>(&self) -> bool - where - C: ?Sized + 'static, - { - (self.0.vtable)(TypeId::of::<C>()).is_some() - } - - /// Create a selector for elements of this function. - pub fn select(self) -> Selector { - Selector::Elem(self, None) - } - - /// Create a selector for elements of this function, filtering for those - /// whose [fields](super::Content::field) match the given arguments. - pub fn where_(self, fields: Dict) -> Selector { - Selector::Elem(self, Some(fields)) - } - - /// Execute the set rule for the element and return the resulting style map. - pub fn set(self, mut args: Args) -> SourceResult<Styles> { - let styles = (self.0.set)(&mut args)?; - args.finish()?; - Ok(styles) - } -} - -impl Debug for ElemFunc { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(self.name()) - } -} - -impl Eq for ElemFunc {} - -impl PartialEq for ElemFunc { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self.0, other.0) - } -} - -impl Hash for ElemFunc { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_usize(self.0 as *const _ as usize); - } -} - -cast! { - ElemFunc, - self => Value::Func(self.into()), - v: Func => v.element().ok_or("expected element function")?, -} - -impl From<&'static NativeElemFunc> for ElemFunc { - fn from(native: &'static NativeElemFunc) -> Self { - Self(native) - } -} - -/// An element function backed by a Rust type. -pub struct NativeElemFunc { - /// The element's name. - pub name: &'static str, - /// The element's vtable for capability dispatch. - pub vtable: fn(of: TypeId) -> Option<*const ()>, - /// The element's constructor. - pub construct: fn(&mut Vm, &mut Args) -> SourceResult<Content>, - /// The element's set rule. - pub set: fn(&mut Args) -> SourceResult<Styles>, - /// Details about the function. - pub info: Lazy<FuncInfo>, -} diff --git a/src/model/introspect.rs b/src/model/introspect.rs deleted file mode 100644 index 42c1a9e1..00000000 --- a/src/model/introspect.rs +++ /dev/null @@ -1,352 +0,0 @@ -use std::cell::RefCell; -use std::collections::HashMap; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::num::NonZeroUsize; - -use comemo::{Prehashed, Track, Tracked, Validate}; -use ecow::EcoVec; -use indexmap::IndexMap; - -use super::{Content, Selector}; -use crate::diag::{bail, StrResult}; -use crate::doc::{Frame, FrameItem, Meta, Position}; -use crate::eval::{cast, Value}; -use crate::geom::{Point, Transform}; -use crate::model::Label; -use crate::util::NonZeroExt; - -/// Identifies the location of an element in the document. -/// -/// This struct is created by [`Locator::locate`]. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Location { - /// The hash of the element. - hash: u128, - /// An unique number among elements with the same hash. This is the reason - /// we need a `Locator` everywhere. - disambiguator: usize, - /// A synthetic location created from another one. This is used for example - /// in bibliography management to create individual linkable locations for - /// reference entries from the bibliography's location. - variant: usize, -} - -impl Location { - /// Produce a variant of this location. - pub fn variant(mut self, n: usize) -> Self { - self.variant = n; - self - } -} - -impl Debug for Location { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("..") - } -} - -cast! { - type Location: "location", -} - -/// Provides locations for elements in the document. -/// -/// A [`Location`] consists of an element's hash plus a disambiguator. Just the -/// hash is not enough because we can have multiple equal elements with the same -/// hash (not a hash collision, just equal elements!). Between these, we -/// disambiguate with an increasing number. In principle, the disambiguator -/// could just be counted up. However, counting is an impure operation and as -/// such we can't count across a memoization boundary. [^1] -/// -/// Instead, we only mutate within a single "layout run" and combine the results -/// with disambiguators from an outer tracked locator. Thus, the locators form a -/// "tracked chain". When a layout run ends, its mutations are discarded and, on -/// the other side of the memoization boundary, we -/// [reconstruct](Self::visit_frame) them from the resulting [frames](Frame). -/// -/// [^1]: Well, we could with [`TrackedMut`](comemo::TrackedMut), but the -/// overhead is quite high, especially since we need to save & undo the counting -/// when only measuring. -#[derive(Default)] -pub struct Locator<'a> { - /// Maps from a hash to the maximum number we've seen for this hash. This - /// number becomes the `disambiguator`. - hashes: RefCell<HashMap<u128, usize>>, - /// An outer `Locator`, from which we can get disambiguator for hashes - /// outside of the current "layout run". - /// - /// We need to override the constraint's lifetime here so that `Tracked` is - /// covariant over the constraint. If it becomes invariant, we're in for a - /// world of lifetime pain. - outer: Option<Tracked<'a, Self, <Locator<'static> as Validate>::Constraint>>, -} - -impl<'a> Locator<'a> { - /// Create a new locator. - pub fn new() -> Self { - Self::default() - } - - /// Create a new chained locator. - pub fn chained(outer: Tracked<'a, Self>) -> Self { - Self { outer: Some(outer), ..Default::default() } - } - - /// Start tracking this locator. - /// - /// In comparison to [`Track::track`], this method skips this chain link - /// if it does not contribute anything. - pub fn track(&self) -> Tracked<'_, Self> { - match self.outer { - Some(outer) if self.hashes.borrow().is_empty() => outer, - _ => Track::track(self), - } - } - - /// Produce a stable identifier for this call site. - pub fn locate(&mut self, hash: u128) -> Location { - // Get the current disambiguator for this hash. - let disambiguator = self.disambiguator_impl(hash); - - // Bump the next disambiguator up by one. - self.hashes.borrow_mut().insert(hash, disambiguator + 1); - - // Create the location in its default variant. - Location { hash, disambiguator, variant: 0 } - } - - /// Advance past a frame. - pub fn visit_frame(&mut self, frame: &Frame) { - for (_, item) in frame.items() { - match item { - FrameItem::Group(group) => self.visit_frame(&group.frame), - FrameItem::Meta(Meta::Elem(elem), _) => { - let mut hashes = self.hashes.borrow_mut(); - let loc = elem.location().unwrap(); - let entry = hashes.entry(loc.hash).or_default(); - - // Next disambiguator needs to be at least one larger than - // the maximum we've seen so far. - *entry = (*entry).max(loc.disambiguator + 1); - } - _ => {} - } - } - } - - /// Advance past a number of frames. - pub fn visit_frames<'b>(&mut self, frames: impl IntoIterator<Item = &'b Frame>) { - for frame in frames { - self.visit_frame(frame); - } - } - - /// The current disambiguator for the given hash. - fn disambiguator_impl(&self, hash: u128) -> usize { - *self - .hashes - .borrow_mut() - .entry(hash) - .or_insert_with(|| self.outer.map_or(0, |outer| outer.disambiguator(hash))) - } -} - -#[comemo::track] -impl<'a> Locator<'a> { - /// The current disambiguator for the hash. - fn disambiguator(&self, hash: u128) -> usize { - self.disambiguator_impl(hash) - } -} - -/// Can be queried for elements and their positions. -pub struct Introspector { - /// The number of pages in the document. - pages: usize, - /// All introspectable elements. - elems: IndexMap<Location, (Prehashed<Content>, Position)>, - /// The page numberings, indexed by page number minus 1. - page_numberings: Vec<Value>, - /// Caches queries done on the introspector. This is important because - /// even if all top-level queries are distinct, they often have shared - /// subqueries. Example: Individual counter queries with `before` that - /// all depend on a global counter query. - queries: RefCell<HashMap<u128, EcoVec<Prehashed<Content>>>>, -} - -impl Introspector { - /// Create a new introspector. - #[tracing::instrument(skip(frames))] - pub fn new(frames: &[Frame]) -> Self { - let mut introspector = Self { - pages: frames.len(), - elems: IndexMap::new(), - page_numberings: vec![], - queries: RefCell::default(), - }; - for (i, frame) in frames.iter().enumerate() { - let page = NonZeroUsize::new(1 + i).unwrap(); - introspector.extract(frame, page, Transform::identity()); - } - introspector - } - - /// Extract metadata from a frame. - #[tracing::instrument(skip_all)] - fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) { - for (pos, item) in frame.items() { - match item { - FrameItem::Group(group) => { - let ts = ts - .pre_concat(Transform::translate(pos.x, pos.y)) - .pre_concat(group.transform); - self.extract(&group.frame, page, ts); - } - FrameItem::Meta(Meta::Elem(content), _) - if !self.elems.contains_key(&content.location().unwrap()) => - { - let pos = pos.transform(ts); - let ret = self.elems.insert( - content.location().unwrap(), - (Prehashed::new(content.clone()), Position { page, point: pos }), - ); - assert!(ret.is_none(), "duplicate locations"); - } - FrameItem::Meta(Meta::PageNumbering(numbering), _) => { - self.page_numberings.push(numbering.clone()); - } - _ => {} - } - } - } - - /// Iterate over all locatable elements. - pub fn all(&self) -> impl Iterator<Item = &Prehashed<Content>> + '_ { - self.elems.values().map(|(c, _)| c) - } - - /// Get an element by its location. - fn get(&self, location: &Location) -> Option<&Prehashed<Content>> { - self.elems.get(location).map(|(elem, _)| elem) - } - - /// Get the index of this element among all. - fn index(&self, elem: &Content) -> usize { - self.elems - .get_index_of(&elem.location().unwrap()) - .unwrap_or(usize::MAX) - } -} - -#[comemo::track] -impl Introspector { - /// Query for all matching elements. - pub fn query(&self, selector: &Selector) -> EcoVec<Prehashed<Content>> { - let hash = crate::util::hash128(selector); - if let Some(output) = self.queries.borrow().get(&hash) { - return output.clone(); - } - - let output = match selector { - Selector::Elem(..) - | Selector::Label(_) - | Selector::Regex(_) - | Selector::Can(_) - | Selector::Or(_) - | Selector::And(_) => { - self.all().filter(|elem| selector.matches(elem)).cloned().collect() - } - - Selector::Location(location) => { - self.get(location).cloned().into_iter().collect() - } - Selector::Before { selector, end, inclusive } => { - let mut list = self.query(selector); - if let Some(end) = self.query_first(end) { - // Determine which elements are before `end`. - let split = match list - .binary_search_by_key(&self.index(&end), |elem| self.index(elem)) - { - // Element itself is contained. - Ok(i) => i + *inclusive as usize, - // Element itself is not contained. - Err(i) => i, - }; - list = list[..split].into(); - } - list - } - Selector::After { selector, start, inclusive } => { - let mut list = self.query(selector); - if let Some(start) = self.query_first(start) { - // Determine which elements are after `start`. - let split = match list - .binary_search_by_key(&self.index(&start), |elem| { - self.index(elem) - }) { - // Element itself is contained. - Ok(i) => i + !*inclusive as usize, - // Element itself is not contained. - Err(i) => i, - }; - list = list[split..].into(); - } - list - } - }; - - self.queries.borrow_mut().insert(hash, output.clone()); - output - } - - /// Query for the first element that matches the selector. - pub fn query_first(&self, selector: &Selector) -> Option<Prehashed<Content>> { - match selector { - Selector::Location(location) => self.get(location).cloned(), - _ => self.query(selector).first().cloned(), - } - } - - /// Query for a unique element with the label. - pub fn query_label(&self, label: &Label) -> StrResult<Prehashed<Content>> { - let mut found = None; - for elem in self.all().filter(|elem| elem.label() == Some(label)) { - if found.is_some() { - bail!("label occurs multiple times in the document"); - } - found = Some(elem.clone()); - } - found.ok_or_else(|| "label does not exist in the document".into()) - } - - /// The total number pages. - pub fn pages(&self) -> NonZeroUsize { - NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE) - } - - /// Gets the page numbering for the given location, if any. - pub fn page_numbering(&self, location: Location) -> Value { - let page = self.page(location); - self.page_numberings.get(page.get() - 1).cloned().unwrap_or_default() - } - - /// Find the page number for the given location. - pub fn page(&self, location: Location) -> NonZeroUsize { - self.position(location).page - } - - /// Find the position for the given location. - pub fn position(&self, location: Location) -> Position { - self.elems - .get(&location) - .map(|(_, loc)| *loc) - .unwrap_or(Position { page: NonZeroUsize::ONE, point: Point::zero() }) - } -} - -impl Default for Introspector { - fn default() -> Self { - Self::new(&[]) - } -} diff --git a/src/model/label.rs b/src/model/label.rs deleted file mode 100644 index ef8f3edd..00000000 --- a/src/model/label.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; - -use ecow::EcoString; - -/// A label for an element. -#[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) - } -} - -/// Indicates that an element cannot be labelled. -pub trait Unlabellable {} diff --git a/src/model/mod.rs b/src/model/mod.rs deleted file mode 100644 index ee940236..00000000 --- a/src/model/mod.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! The document model. - -mod content; -mod element; -mod introspect; -mod label; -mod realize; -mod selector; -mod styles; - -#[doc(inline)] -pub use typst_macros::element; - -pub use self::content::{Content, MetaElem, PlainText}; -pub use self::element::{Construct, ElemFunc, Element, NativeElemFunc, Set}; -pub use self::introspect::{Introspector, Location, Locator}; -pub use self::label::{Label, Unlabellable}; -pub use self::realize::{ - applicable, realize, Behave, Behaviour, Finalize, Guard, Locatable, Show, Synthesize, -}; -pub use self::selector::{LocatableSelector, Selector, ShowableSelector}; -pub use self::styles::{ - Fold, Property, Recipe, Resolve, Style, StyleChain, StyleVec, StyleVecBuilder, - Styles, Transform, -}; - -use std::mem::ManuallyDrop; - -use comemo::{Track, Tracked, TrackedMut, Validate}; - -use crate::diag::{SourceError, SourceResult}; -use crate::doc::Document; -use crate::eval::Tracer; -use crate::World; - -/// Typeset content into a fully layouted document. -#[comemo::memoize] -#[tracing::instrument(skip(world, tracer, content))] -pub fn typeset( - world: Tracked<dyn World + '_>, - mut tracer: TrackedMut<Tracer>, - content: &Content, -) -> SourceResult<Document> { - tracing::info!("Starting typesetting"); - - let library = world.library(); - let styles = StyleChain::new(&library.styles); - - let mut iter = 0; - let mut document; - let mut delayed; - - // We need `ManuallyDrop` until this lands in stable: - // https://github.com/rust-lang/rust/issues/70919 - let mut introspector = ManuallyDrop::new(Introspector::new(&[])); - - // Relayout until all introspections stabilize. - // If that doesn't happen within five attempts, we give up. - loop { - tracing::info!("Layout iteration {iter}"); - - delayed = DelayedErrors::default(); - - let constraint = <Introspector as Validate>::Constraint::new(); - let mut locator = Locator::new(); - let mut vt = Vt { - world, - tracer: TrackedMut::reborrow_mut(&mut tracer), - locator: &mut locator, - introspector: introspector.track_with(&constraint), - delayed: delayed.track_mut(), - }; - - // Layout! - let result = (library.items.layout)(&mut vt, content, styles)?; - - // Drop the old introspector. - ManuallyDrop::into_inner(introspector); - - // Only now assign the document and construct the new introspector. - document = result; - introspector = ManuallyDrop::new(Introspector::new(&document.pages)); - iter += 1; - - if iter >= 5 || introspector.validate(&constraint) { - break; - } - } - - // Drop the introspector. - ManuallyDrop::into_inner(introspector); - - // Promote delayed errors. - if !delayed.0.is_empty() { - return Err(Box::new(delayed.0)); - } - - Ok(document) -} - -/// A virtual typesetter. -/// -/// Holds the state needed to [typeset] content. -pub struct Vt<'a> { - /// The compilation environment. - pub world: Tracked<'a, dyn World + 'a>, - /// Provides access to information about the document. - pub introspector: Tracked<'a, Introspector>, - /// Provides stable identities to elements. - pub locator: &'a mut Locator<'a>, - /// Delayed errors that do not immediately terminate execution. - pub delayed: TrackedMut<'a, DelayedErrors>, - /// The tracer for inspection of the values an expression produces. - pub tracer: TrackedMut<'a, Tracer>, -} - -impl Vt<'_> { - /// Perform a fallible operation that does not immediately terminate further - /// execution. Instead it produces a delayed error that is only promoted to - /// a fatal one if it remains at the end of the introspection loop. - pub fn delayed<F, T>(&mut self, f: F) -> T - where - F: FnOnce(&mut Self) -> SourceResult<T>, - T: Default, - { - match f(self) { - Ok(value) => value, - Err(errors) => { - for error in *errors { - self.delayed.push(error); - } - T::default() - } - } - } -} - -/// Holds delayed errors. -#[derive(Default, Clone)] -pub struct DelayedErrors(Vec<SourceError>); - -#[comemo::track] -impl DelayedErrors { - /// Push a delayed error. - fn push(&mut self, error: SourceError) { - self.0.push(error); - } -} diff --git a/src/model/realize.rs b/src/model/realize.rs deleted file mode 100644 index 01c46b81..00000000 --- a/src/model/realize.rs +++ /dev/null @@ -1,228 +0,0 @@ -use super::{Content, ElemFunc, Element, MetaElem, Recipe, Selector, StyleChain, Vt}; -use crate::diag::SourceResult; -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 { - if target.needs_preparation() { - return true; - } - - if target.can::<dyn Show>() && target.is_pristine() { - return true; - } - - // Find out how many recipes there are. - let mut n = styles.recipes().count(); - - // Find out whether any recipe matches and is unguarded. - for recipe in styles.recipes() { - if recipe.applicable(target) && !target.is_guarded(Guard::Nth(n)) { - return true; - } - n -= 1; - } - - false -} - -/// Apply the show rules in the given style chain to a target. -pub fn realize( - vt: &mut Vt, - target: &Content, - styles: StyleChain, -) -> SourceResult<Option<Content>> { - // Pre-process. - if target.needs_preparation() { - let mut elem = target.clone(); - if target.can::<dyn Locatable>() || target.label().is_some() { - let location = vt.locator.locate(hash128(target)); - elem.set_location(location); - } - - if let Some(elem) = elem.with_mut::<dyn Synthesize>() { - elem.synthesize(vt, styles)?; - } - - elem.mark_prepared(); - - if elem.location().is_some() { - let span = elem.span(); - let meta = Meta::Elem(elem.clone()); - return Ok(Some( - (elem + MetaElem::new().pack().spanned(span)) - .styled(MetaElem::set_data(vec![meta])), - )); - } - - return Ok(Some(elem)); - } - - // Find out how many recipes there are. - let mut n = styles.recipes().count(); - - // Find an applicable recipe. - let mut realized = None; - for recipe in styles.recipes() { - let guard = Guard::Nth(n); - if recipe.applicable(target) && !target.is_guarded(guard) { - if let Some(content) = try_apply(vt, target, recipe, guard)? { - realized = Some(content); - break; - } - } - n -= 1; - } - - // Realize if there was no matching recipe. - if let Some(showable) = target.with::<dyn Show>() { - let guard = Guard::Base(target.func()); - if realized.is_none() && !target.is_guarded(guard) { - realized = Some(showable.show(vt, styles)?); - } - } - - // Finalize only if this is the first application for this element. - if let Some(elem) = target.with::<dyn Finalize>() { - if target.is_pristine() { - if let Some(already) = realized { - realized = Some(elem.finalize(already, styles)); - } - } - } - - Ok(realized) -} - -/// Try to apply a recipe to the target. -fn try_apply( - vt: &mut Vt, - target: &Content, - recipe: &Recipe, - guard: Guard, -) -> SourceResult<Option<Content>> { - match &recipe.selector { - Some(Selector::Elem(element, _)) => { - if target.func() != *element { - return Ok(None); - } - - recipe.apply_vt(vt, target.clone().guarded(guard)).map(Some) - } - - Some(Selector::Label(label)) => { - if target.label() != Some(label) { - return Ok(None); - } - - recipe.apply_vt(vt, target.clone().guarded(guard)).map(Some) - } - - Some(Selector::Regex(regex)) => { - let Some(text) = item!(text_str)(target) else { - return Ok(None); - }; - - let make = |s: &str| target.clone().with_field("text", s); - let mut result = vec![]; - let mut cursor = 0; - - for m in regex.find_iter(&text) { - let start = m.start(); - if cursor < start { - result.push(make(&text[cursor..start])); - } - - let piece = make(m.as_str()).guarded(guard); - let transformed = recipe.apply_vt(vt, piece)?; - result.push(transformed); - cursor = m.end(); - } - - if result.is_empty() { - return Ok(None); - } - - if cursor < text.len() { - result.push(make(&text[cursor..])); - } - - Ok(Some(Content::sequence(result))) - } - - // Not supported here. - Some( - Selector::Or(_) - | Selector::And(_) - | Selector::Location(_) - | Selector::Can(_) - | Selector::Before { .. } - | Selector::After { .. }, - ) => Ok(None), - - None => Ok(None), - } -} - -/// Makes this element locatable through `vt.locate`. -pub trait Locatable {} - -/// Synthesize fields on an element. This happens before execution of any show -/// rule. -pub trait Synthesize { - /// Prepare the element for show rule application. - fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()>; -} - -/// The base recipe for an element. -pub trait Show { - /// Execute the base recipe for this element. - fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content>; -} - -/// Post-process an element after it was realized. -pub trait Finalize { - /// Finalize the fully realized form of the element. Use this for effects - /// that should work even in the face of a user-defined show rule. - fn finalize(&self, realized: Content, styles: StyleChain) -> Content; -} - -/// How the element interacts with other elements. -pub trait Behave { - /// The element's interaction behaviour. - fn behaviour(&self) -> Behaviour; - - /// Whether this weak element 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 an element interacts with other elements in a stream. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Behaviour { - /// A weak element which only survives when a supportive element is before - /// and after it. Furthermore, per consecutive run of weak elements, only - /// one survives: The one with the lowest weakness level (or the larger one - /// if there is a tie). - Weak(usize), - /// An element that enables adjacent weak elements to exist. The default. - Supportive, - /// An element that destroys adjacent weak elements. - Destructive, - /// An element that does not interact at all with other elements, 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 { - /// The nth recipe from the top of the chain. - Nth(usize), - /// The [base recipe](Show) for a kind of element. - Base(ElemFunc), -} diff --git a/src/model/selector.rs b/src/model/selector.rs deleted file mode 100644 index 9723ee4f..00000000 --- a/src/model/selector.rs +++ /dev/null @@ -1,296 +0,0 @@ -use std::any::{Any, TypeId}; -use std::fmt::{self, Debug, Formatter, Write}; -use std::sync::Arc; - -use ecow::{eco_format, EcoString, EcoVec}; - -use super::{Content, ElemFunc, Label, Location}; -use crate::diag::{bail, StrResult}; -use crate::eval::{ - cast, CastInfo, Dict, FromValue, Func, IntoValue, Reflect, Regex, Value, -}; -use crate::model::Locatable; -use crate::util::pretty_array_like; - -/// A selector in a show rule. -#[derive(Clone, PartialEq, Hash)] -pub enum Selector { - /// Matches a specific type of element. - /// - /// If there is a dictionary, only elements with the fields from the - /// dictionary match. - Elem(ElemFunc, Option<Dict>), - /// Matches the element at the specified location. - Location(Location), - /// Matches elements with a specific label. - Label(Label), - /// Matches text elements through a regular expression. - Regex(Regex), - /// Matches elements with a specific capability. - Can(TypeId), - /// Matches if any of the subselectors match. - Or(EcoVec<Self>), - /// Matches if all of the subselectors match. - And(EcoVec<Self>), - /// Matches all matches of `selector` before `end`. - Before { selector: Arc<Self>, end: Arc<Self>, inclusive: bool }, - /// Matches all matches of `selector` after `start`. - After { selector: Arc<Self>, start: Arc<Self>, inclusive: bool }, -} - -impl Selector { - /// Define a simple text selector. - pub fn text(text: &str) -> Self { - Self::Regex(Regex::new(®ex::escape(text)).unwrap()) - } - - /// Define a simple [`Selector::Can`] selector. - pub fn can<T: ?Sized + Any>() -> Self { - Self::Can(TypeId::of::<T>()) - } - - /// Transforms this selector and an iterator of other selectors into a - /// [`Selector::Or`] selector. - pub fn and(self, others: impl IntoIterator<Item = Self>) -> Self { - Self::And(others.into_iter().chain(Some(self)).collect()) - } - - /// Transforms this selector and an iterator of other selectors into a - /// [`Selector::And`] selector. - pub fn or(self, others: impl IntoIterator<Item = Self>) -> Self { - Self::Or(others.into_iter().chain(Some(self)).collect()) - } - - /// Transforms this selector into a [`Selector::Before`] selector. - pub fn before(self, location: impl Into<Self>, inclusive: bool) -> Self { - Self::Before { - selector: Arc::new(self), - end: Arc::new(location.into()), - inclusive, - } - } - - /// Transforms this selector into a [`Selector::After`] selector. - pub fn after(self, location: impl Into<Self>, inclusive: bool) -> Self { - Self::After { - selector: Arc::new(self), - start: Arc::new(location.into()), - inclusive, - } - } - - /// Whether the selector matches for the target. - pub fn matches(&self, target: &Content) -> bool { - match self { - Self::Elem(element, dict) => { - target.func() == *element - && dict - .iter() - .flat_map(|dict| dict.iter()) - .all(|(name, value)| target.field_ref(name) == Some(value)) - } - Self::Label(label) => target.label() == Some(label), - Self::Regex(regex) => { - target.func() == item!(text_func) - && item!(text_str)(target).map_or(false, |text| regex.is_match(&text)) - } - Self::Can(cap) => target.can_type_id(*cap), - Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)), - Self::And(selectors) => selectors.iter().all(move |sel| sel.matches(target)), - Self::Location(location) => target.location() == Some(*location), - // Not supported here. - Self::Before { .. } | Self::After { .. } => false, - } - } -} - -impl From<Location> for Selector { - fn from(value: Location) -> Self { - Self::Location(value) - } -} - -impl Debug for Selector { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Elem(elem, dict) => { - f.write_str(elem.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), - Self::Can(cap) => cap.fmt(f), - Self::Or(selectors) | Self::And(selectors) => { - f.write_str(if matches!(self, Self::Or(_)) { "or" } else { "and" })?; - let pieces: Vec<_> = - selectors.iter().map(|sel| eco_format!("{sel:?}")).collect(); - f.write_str(&pretty_array_like(&pieces, false)) - } - Self::Location(loc) => loc.fmt(f), - Self::Before { selector, end: split, inclusive } - | Self::After { selector, start: split, inclusive } => { - selector.fmt(f)?; - - if matches!(self, Self::Before { .. }) { - f.write_str(".before(")?; - } else { - f.write_str(".after(")?; - } - - split.fmt(f)?; - if !*inclusive { - f.write_str(", inclusive: false")?; - } - f.write_char(')') - } - } - } -} - -cast! { - type Selector: "selector", - func: Func => func - .element() - .ok_or("only element functions can be used as selectors")? - .select(), - label: Label => Self::Label(label), - text: EcoString => Self::text(&text), - regex: Regex => Self::Regex(regex), - location: Location => Self::Location(location), -} - -/// A selector that can be used with `query`. -/// -/// Hopefully, this is made obsolete by a more powerful query mechanism in the -/// future. -#[derive(Clone, PartialEq, Hash)] -pub struct LocatableSelector(pub Selector); - -impl Reflect for LocatableSelector { - fn describe() -> CastInfo { - CastInfo::Union(vec![ - CastInfo::Type("function"), - CastInfo::Type("label"), - CastInfo::Type("selector"), - ]) - } - - fn castable(value: &Value) -> bool { - matches!(value.type_name(), "function" | "label" | "selector") - } -} - -impl IntoValue for LocatableSelector { - fn into_value(self) -> Value { - self.0.into_value() - } -} - -impl FromValue for LocatableSelector { - fn from_value(value: Value) -> StrResult<Self> { - fn validate(selector: &Selector) -> StrResult<()> { - match selector { - Selector::Elem(elem, _) => { - if !elem.can::<dyn Locatable>() { - Err(eco_format!("{} is not locatable", elem.name()))? - } - } - Selector::Location(_) => {} - Selector::Label(_) => {} - Selector::Regex(_) => bail!("text is not locatable"), - Selector::Can(_) => bail!("capability is not locatable"), - Selector::Or(list) | Selector::And(list) => { - for selector in list { - validate(selector)?; - } - } - Selector::Before { selector, end: split, .. } - | Selector::After { selector, start: split, .. } => { - for selector in [selector, split] { - validate(selector)?; - } - } - } - Ok(()) - } - - if !Self::castable(&value) { - return Err(Self::error(&value)); - } - - let selector = Selector::from_value(value)?; - validate(&selector)?; - Ok(Self(selector)) - } -} - -/// A selector that can be used with show rules. -/// -/// Hopefully, this is made obsolete by a more powerful showing mechanism in the -/// future. -#[derive(Clone, PartialEq, Hash)] -pub struct ShowableSelector(pub Selector); - -impl Reflect for ShowableSelector { - fn describe() -> CastInfo { - CastInfo::Union(vec![ - CastInfo::Type("function"), - CastInfo::Type("label"), - CastInfo::Type("string"), - CastInfo::Type("regular expression"), - CastInfo::Type("symbol"), - CastInfo::Type("selector"), - ]) - } - - fn castable(value: &Value) -> bool { - matches!( - value.type_name(), - "symbol" - | "string" - | "label" - | "function" - | "regular expression" - | "selector" - ) - } -} - -impl IntoValue for ShowableSelector { - fn into_value(self) -> Value { - self.0.into_value() - } -} - -impl FromValue for ShowableSelector { - fn from_value(value: Value) -> StrResult<Self> { - fn validate(selector: &Selector) -> StrResult<()> { - match selector { - Selector::Elem(_, _) => {} - Selector::Label(_) => {} - Selector::Regex(_) => {} - Selector::Or(_) - | Selector::And(_) - | Selector::Location(_) - | Selector::Can(_) - | Selector::Before { .. } - | Selector::After { .. } => { - bail!("this selector cannot be used with show") - } - } - Ok(()) - } - - if !Self::castable(&value) { - return Err(Self::error(&value)); - } - - let selector = Selector::from_value(value)?; - validate(&selector)?; - Ok(Self(selector)) - } -} diff --git a/src/model/styles.rs b/src/model/styles.rs deleted file mode 100644 index 23748a3f..00000000 --- a/src/model/styles.rs +++ /dev/null @@ -1,750 +0,0 @@ -use std::fmt::{self, Debug, Formatter, Write}; -use std::iter; -use std::mem; -use std::ptr; - -use comemo::Prehashed; -use ecow::{eco_vec, EcoString, EcoVec}; - -use super::{Content, ElemFunc, Element, Selector, Vt}; -use crate::diag::{SourceResult, Trace, Tracepoint}; -use crate::eval::{cast, Args, FromValue, Func, IntoValue, Value, Vm}; -use crate::syntax::Span; - -/// A list of style properties. -#[derive(Default, PartialEq, Clone, Hash)] -pub struct Styles(EcoVec<Prehashed<Style>>); - -impl Styles { - /// Create a new, empty style list. - pub fn new() -> Self { - Self::default() - } - - /// Whether this contains no styles. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Set an inner value for a style property. - /// - /// 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, style: impl Into<Style>) { - self.0.push(Prehashed::new(style.into())); - } - - /// Remove the style that was last set. - pub fn unset(&mut self) { - self.0.pop(); - } - - /// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place. - pub fn apply(&mut self, mut outer: Self) { - outer.0.extend(mem::take(self).0.into_iter()); - *self = outer; - } - - /// Apply one outer styles. - pub fn apply_one(&mut self, outer: Style) { - self.0.insert(0, Prehashed::new(outer)); - } - - /// Apply a slice of outer styles. - pub fn apply_slice(&mut self, outer: &[Prehashed<Style>]) { - self.0 = outer.iter().cloned().chain(mem::take(self).0.into_iter()).collect(); - } - - /// Add an origin span to all contained properties. - pub fn spanned(mut self, span: Span) -> Self { - for entry in self.0.make_mut() { - entry.update(|entry| { - if let Style::Property(property) = entry { - property.span = Some(span); - } - }); - } - self - } - - /// Returns `Some(_)` with an optional span if this list contains - /// styles for the given element. - pub fn interruption<T: Element>(&self) -> Option<Option<Span>> { - let func = T::func(); - self.0.iter().find_map(|entry| match &**entry { - Style::Property(property) => property.is_of(func).then_some(property.span), - Style::Recipe(recipe) => recipe.is_of(func).then_some(Some(recipe.span)), - }) - } -} - -impl From<Style> for Styles { - fn from(entry: Style) -> Self { - Self(eco_vec![Prehashed::new(entry)]) - } -} - -impl Debug for Styles { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("..") - } -} - -/// A single style property or recipe. -#[derive(Clone, PartialEq, Hash)] -pub enum Style { - /// A style property originating from a set rule or constructor. - Property(Property), - /// A show rule recipe. - Recipe(Recipe), -} - -impl Style { - /// If this is a property, return it. - pub fn property(&self) -> Option<&Property> { - match self { - Self::Property(property) => Some(property), - _ => None, - } - } - - /// If this is a recipe, return it. - pub fn recipe(&self) -> Option<&Recipe> { - match self { - Self::Recipe(recipe) => Some(recipe), - _ => None, - } - } -} - -impl Debug for Style { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Property(property) => property.fmt(f), - Self::Recipe(recipe) => recipe.fmt(f), - } - } -} - -impl From<Property> for Style { - fn from(property: Property) -> Self { - Self::Property(property) - } -} - -impl From<Recipe> for Style { - fn from(recipe: Recipe) -> Self { - Self::Recipe(recipe) - } -} - -/// A style property originating from a set rule or constructor. -#[derive(Clone, PartialEq, Hash)] -pub struct Property { - /// The element the property belongs to. - element: ElemFunc, - /// The property's name. - name: EcoString, - /// The property's value. - value: Value, - /// The span of the set rule the property stems from. - span: Option<Span>, -} - -impl Property { - /// Create a new property from a key-value pair. - pub fn new( - element: ElemFunc, - name: impl Into<EcoString>, - value: impl IntoValue, - ) -> Self { - Self { - element, - name: name.into(), - value: value.into_value(), - span: None, - } - } - - /// Whether this property is the given one. - pub fn is(&self, element: ElemFunc, name: &str) -> bool { - self.element == element && self.name == name - } - - /// Whether this property belongs to the given element. - pub fn is_of(&self, element: ElemFunc) -> bool { - self.element == element - } -} - -impl Debug for Property { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "set {}({}: {:?})", self.element.name(), self.name, self.value)?; - Ok(()) - } -} - -/// A show rule recipe. -#[derive(Clone, PartialEq, Hash)] -pub struct Recipe { - /// The span errors are reported with. - pub span: Span, - /// Determines whether the recipe applies to an element. - pub selector: Option<Selector>, - /// The transformation to perform on the match. - pub transform: Transform, -} - -impl Recipe { - /// Whether this recipe is for the given type of element. - pub fn is_of(&self, element: ElemFunc) -> bool { - match self.selector { - Some(Selector::Elem(own, _)) => own == element, - _ => false, - } - } - - /// Whether the recipe is applicable to the target. - pub fn applicable(&self, target: &Content) -> bool { - self.selector - .as_ref() - .map_or(false, |selector| selector.matches(target)) - } - - /// Apply the recipe to the given content. - pub fn apply_vm(&self, vm: &mut Vm, content: Content) -> SourceResult<Content> { - match &self.transform { - Transform::Content(content) => Ok(content.clone()), - Transform::Func(func) => { - let args = Args::new(self.span, [Value::Content(content.clone())]); - let mut result = func.call_vm(vm, args); - // For selector-less show rules, a tracepoint makes no sense. - if self.selector.is_some() { - let point = || Tracepoint::Show(content.func().name().into()); - result = result.trace(vm.world(), point, content.span()); - } - Ok(result?.display()) - } - Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())), - } - } - - /// Apply the recipe to the given content. - pub fn apply_vt(&self, vt: &mut Vt, content: Content) -> SourceResult<Content> { - match &self.transform { - Transform::Content(content) => Ok(content.clone()), - Transform::Func(func) => { - let mut result = func.call_vt(vt, [Value::Content(content.clone())]); - if self.selector.is_some() { - let point = || Tracepoint::Show(content.func().name().into()); - result = result.trace(vt.world, point, content.span()); - } - Ok(result?.display()) - } - Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())), - } - } -} - -impl Debug for Recipe { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("show")?; - if let Some(selector) = &self.selector { - f.write_char(' ')?; - selector.fmt(f)?; - } - f.write_str(": ")?; - self.transform.fmt(f) - } -} - -/// A show rule transformation that can be applied to a match. -#[derive(Clone, PartialEq, Hash)] -pub enum Transform { - /// Replacement content. - Content(Content), - /// A function to apply to the match. - Func(Func), - /// Apply styles to the content. - Style(Styles), -} - -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! { - Transform, - content: Content => Self::Content(content), - func: Func => Self::Func(func), -} - -/// A chain of styles, similar to a linked list. -/// -/// A style chain allows to combine properties from multiple style lists in a -/// element hierarchy in a non-allocating way. Rather than eagerly merging the -/// lists, each access walks the hierarchy from the innermost to the outermost -/// map, trying to find a match and then folding it with matches further up the -/// chain. -#[derive(Default, Clone, Copy, Hash)] -pub struct StyleChain<'a> { - /// The first link of this chain. - head: &'a [Prehashed<Style>], - /// The remaining links in the chain. - tail: Option<&'a Self>, -} - -impl<'a> StyleChain<'a> { - /// Start a new style chain with root styles. - pub fn new(root: &'a Styles) -> Self { - Self { head: &root.0, tail: None } - } - - /// Make the given style list the first link of this chain. - /// - /// The resulting style chain contains styles from `local` as well as - /// `self`. The ones from `local` take precedence over the ones from - /// `self`. For folded properties `local` contributes the inner value. - pub fn chain<'b>(&'b self, local: &'b Styles) -> StyleChain<'b> { - if local.is_empty() { - *self - } else { - StyleChain { head: &local.0, tail: Some(self) } - } - } - - /// Cast the first value for the given property in the chain. - pub fn get<T: FromValue>( - self, - func: ElemFunc, - name: &'a str, - inherent: Option<Value>, - default: impl Fn() -> T, - ) -> T { - self.properties::<T>(func, name, inherent) - .next() - .unwrap_or_else(default) - } - - /// Cast the first value for the given property in the chain. - pub fn get_resolve<T: FromValue + Resolve>( - self, - func: ElemFunc, - name: &'a str, - inherent: Option<Value>, - default: impl Fn() -> T, - ) -> T::Output { - self.get(func, name, inherent, default).resolve(self) - } - - /// Cast the first value for the given property in the chain. - pub fn get_fold<T: FromValue + Fold>( - self, - func: ElemFunc, - name: &'a str, - inherent: Option<Value>, - default: impl Fn() -> T::Output, - ) -> T::Output { - fn next<T: Fold>( - mut values: impl Iterator<Item = T>, - _styles: StyleChain, - default: &impl Fn() -> T::Output, - ) -> T::Output { - values - .next() - .map(|value| value.fold(next(values, _styles, default))) - .unwrap_or_else(default) - } - next(self.properties::<T>(func, name, inherent), self, &default) - } - - /// Cast the first value for the given property in the chain. - pub fn get_resolve_fold<T>( - self, - func: ElemFunc, - name: &'a str, - inherent: Option<Value>, - default: impl Fn() -> <T::Output as Fold>::Output, - ) -> <T::Output as Fold>::Output - where - T: FromValue + Resolve, - T::Output: Fold, - { - fn next<T>( - mut values: impl Iterator<Item = T>, - styles: StyleChain, - default: &impl Fn() -> <T::Output as Fold>::Output, - ) -> <T::Output as Fold>::Output - where - T: Resolve, - T::Output: Fold, - { - values - .next() - .map(|value| value.resolve(styles).fold(next(values, styles, default))) - .unwrap_or_else(default) - } - next(self.properties::<T>(func, name, inherent), self, &default) - } - - /// Iterate over all style recipes in the chain. - pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> { - self.entries().filter_map(Style::recipe) - } - - /// Iterate over all values for the given property in the chain. - pub fn properties<T: FromValue + 'a>( - self, - func: ElemFunc, - name: &'a str, - inherent: Option<Value>, - ) -> impl Iterator<Item = T> + '_ { - inherent - .into_iter() - .chain( - self.entries() - .filter_map(Style::property) - .filter(move |property| property.is(func, name)) - .map(|property| property.value.clone()), - ) - .map(move |value| { - value.cast().unwrap_or_else(|err| { - panic!("{} (for {}.{})", err, func.name(), name) - }) - }) - } - - /// Convert to a style map. - pub fn to_map(self) -> Styles { - let mut suffix = Styles::new(); - for link in self.links() { - suffix.apply_slice(link); - } - suffix - } - - /// Iterate over the entries of the chain. - fn entries(self) -> Entries<'a> { - Entries { inner: [].as_slice().iter(), links: self.links() } - } - - /// Iterate over the links of the chain. - fn links(self) -> Links<'a> { - Links(Some(self)) - } - - /// Build owned styles from the suffix (all links beyond the `len`) of the - /// chain. - fn suffix(self, len: usize) -> Styles { - let mut suffix = Styles::new(); - let take = self.links().count().saturating_sub(len); - for link in self.links().take(take) { - suffix.apply_slice(link); - } - suffix - } - - /// Remove the last link from the chain. - fn pop(&mut self) { - *self = self.tail.copied().unwrap_or_default(); - } -} - -impl Debug for StyleChain<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for entry in self.entries().collect::<Vec<_>>().into_iter().rev() { - writeln!(f, "{:?}", entry)?; - } - Ok(()) - } -} - -impl PartialEq for StyleChain<'_> { - fn eq(&self, other: &Self) -> bool { - ptr::eq(self.head, other.head) - && match (self.tail, other.tail) { - (Some(a), Some(b)) => ptr::eq(a, b), - (None, None) => true, - _ => false, - } - } -} - -/// An iterator over the entries in a style chain. -struct Entries<'a> { - inner: std::slice::Iter<'a, Prehashed<Style>>, - links: Links<'a>, -} - -impl<'a> Iterator for Entries<'a> { - type Item = &'a Style; - - fn next(&mut self) -> Option<Self::Item> { - loop { - if let Some(entry) = self.inner.next_back() { - return Some(entry); - } - - match self.links.next() { - Some(next) => self.inner = next.iter(), - None => return None, - } - } - } -} - -/// An iterator over the links of a style chain. -struct Links<'a>(Option<StyleChain<'a>>); - -impl<'a> Iterator for Links<'a> { - type Item = &'a [Prehashed<Style>]; - - fn next(&mut self) -> Option<Self::Item> { - let StyleChain { head, tail } = self.0?; - self.0 = tail.copied(); - Some(head) - } -} - -/// A sequence of items with associated styles. -#[derive(Clone, Hash)] -pub struct StyleVec<T> { - items: Vec<T>, - styles: Vec<(Styles, usize)>, -} - -impl<T> StyleVec<T> { - /// Whether there are any items in the sequence. - pub fn is_empty(&self) -> bool { - self.items.is_empty() - } - - /// Number of items in the sequence. - pub fn len(&self) -> usize { - self.items.len() - } - - /// Insert an item in the front. The item will share the style of the - /// current first item. - /// - /// This method has no effect if the vector is empty. - pub fn push_front(&mut self, item: T) { - if !self.styles.is_empty() { - self.items.insert(0, item); - self.styles[0].1 += 1; - } - } - - /// Map the contained items. - pub fn map<F, U>(&self, f: F) -> StyleVec<U> - where - F: FnMut(&T) -> U, - { - StyleVec { - items: self.items.iter().map(f).collect(), - styles: self.styles.clone(), - } - } - - /// Iterate over references to the contained items and associated styles. - pub fn iter(&self) -> impl Iterator<Item = (&T, &Styles)> + '_ { - self.items().zip( - self.styles - .iter() - .flat_map(|(map, count)| iter::repeat(map).take(*count)), - ) - } - - /// Iterate over the contained items. - pub fn items(&self) -> std::slice::Iter<'_, T> { - self.items.iter() - } - - /// Iterate over the contained style lists. Note that zipping this with - /// `items()` does not yield the same result as calling `iter()` because - /// this method only returns lists once that are shared by consecutive - /// items. This method is designed for use cases where you want to check, - /// for example, whether any of the lists fulfills a specific property. - pub fn styles(&self) -> impl Iterator<Item = &Styles> { - self.styles.iter().map(|(map, _)| map) - } -} - -impl StyleVec<Content> { - pub fn to_vec(self) -> Vec<Content> { - self.items - .into_iter() - .zip( - self.styles - .iter() - .flat_map(|(map, count)| iter::repeat(map).take(*count)), - ) - .map(|(content, styles)| content.styled_with_map(styles.clone())) - .collect() - } -} - -impl<T> Default for StyleVec<T> { - fn default() -> Self { - Self { items: vec![], styles: vec![] } - } -} - -impl<T> FromIterator<T> for StyleVec<T> { - fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { - let items: Vec<_> = iter.into_iter().collect(); - let styles = vec![(Styles::new(), items.len())]; - Self { items, styles } - } -} - -impl<T: Debug> Debug for StyleVec<T> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_list() - .entries(self.iter().map(|(item, styles)| { - crate::util::debug(|f| { - styles.fmt(f)?; - item.fmt(f) - }) - })) - .finish() - } -} - -/// Assists in the construction of a [`StyleVec`]. -#[derive(Debug)] -pub struct StyleVecBuilder<'a, T> { - items: Vec<T>, - chains: Vec<(StyleChain<'a>, usize)>, -} - -impl<'a, T> StyleVecBuilder<'a, T> { - /// Create a new style-vec builder. - pub fn new() -> Self { - Self { items: vec![], chains: vec![] } - } - - /// Whether the builder is empty. - pub fn is_empty(&self) -> bool { - self.items.is_empty() - } - - /// Push a new item into the style vector. - pub fn push(&mut self, item: T, styles: StyleChain<'a>) { - self.items.push(item); - - if let Some((prev, count)) = self.chains.last_mut() { - if *prev == styles { - *count += 1; - return; - } - } - - self.chains.push((styles, 1)); - } - - /// Iterate over the contained items. - pub fn elems(&self) -> std::slice::Iter<'_, T> { - self.items.iter() - } - - /// Finish building, returning a pair of two things: - /// - a style vector of items with the non-shared styles - /// - a shared prefix chain of styles that apply to all items - pub fn finish(self) -> (StyleVec<T>, StyleChain<'a>) { - let mut iter = self.chains.iter(); - let mut trunk = match iter.next() { - Some(&(chain, _)) => chain, - None => return Default::default(), - }; - - let mut shared = trunk.links().count(); - for &(mut chain, _) in iter { - let len = chain.links().count(); - if len < shared { - for _ in 0..shared - len { - trunk.pop(); - } - shared = len; - } else if len > shared { - for _ in 0..len - shared { - chain.pop(); - } - } - - while shared > 0 && chain != trunk { - trunk.pop(); - chain.pop(); - shared -= 1; - } - } - - let styles = self - .chains - .into_iter() - .map(|(chain, count)| (chain.suffix(shared), count)) - .collect(); - - (StyleVec { items: self.items, styles }, trunk) - } -} - -impl<'a, T> Default for StyleVecBuilder<'a, T> { - fn default() -> Self { - Self::new() - } -} - -/// A property that is resolved with other properties from the style chain. -pub trait Resolve { - /// The type of the resolved output. - type Output; - - /// Resolve the value using the style chain. - fn resolve(self, styles: StyleChain) -> Self::Output; -} - -impl<T: Resolve> Resolve for Option<T> { - type Output = Option<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -/// A property that is folded to determine its final value. -/// -/// In the example below, the chain of stroke values is folded into a single -/// value: `4pt + red`. -/// -/// ```example -/// #set rect(stroke: red) -/// #set rect(stroke: 4pt) -/// #rect() -/// ``` -pub trait Fold { - /// The type of the folded output. - type Output; - - /// Fold this inner value with an outer folded value. - fn fold(self, outer: Self::Output) -> Self::Output; -} - -impl<T> Fold for Option<T> -where - T: Fold, - T::Output: Default, -{ - type Output = Option<T::Output>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) - } -} diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs deleted file mode 100644 index 7d5e2989..00000000 --- a/src/syntax/ast.rs +++ /dev/null @@ -1,1994 +0,0 @@ -//! A typed layer over the untyped syntax tree. -//! -//! The AST is rooted in the [`Markup`] node. - -use std::num::NonZeroUsize; -use std::ops::Deref; - -use ecow::EcoString; -use unscanny::Scanner; - -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 { - /// Convert a node into its typed variant. - fn from_untyped(node: &SyntaxNode) -> Option<Self>; - - /// A reference to the underlying syntax node. - fn as_untyped(&self) -> &SyntaxNode; - - /// The source code location. - fn span(&self) -> Span { - self.as_untyped().span() - } -} - -macro_rules! node { - ($(#[$attr:meta])* $name:ident) => { - #[derive(Debug, Default, Clone, Hash)] - #[repr(transparent)] - $(#[$attr])* - pub struct $name(SyntaxNode); - - impl AstNode for $name { - fn from_untyped(node: &SyntaxNode) -> Option<Self> { - if matches!(node.kind(), SyntaxKind::$name) { - Some(Self(node.clone())) - } else { - Option::None - } - } - - fn as_untyped(&self) -> &SyntaxNode { - &self.0 - } - } - }; -} - -node! { - /// The syntactical root capable of representing a full parsed document. - Markup -} - -impl Markup { - /// The expressions. - pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ { - let mut was_stmt = false; - self.0 - .children() - .filter(move |node| { - // Ignore newline directly after statements without semicolons. - let kind = node.kind(); - let keep = !was_stmt || node.kind() != SyntaxKind::Space; - was_stmt = kind.is_stmt(); - keep - }) - .filter_map(Expr::cast_with_space) - } -} - -/// An expression in markup, math or code. -#[derive(Debug, Clone, Hash)] -pub enum Expr { - /// Plain text without markup. - Text(Text), - /// Whitespace in markup or math. Has at most one newline in markup, as more - /// indicate a paragraph break. - Space(Space), - /// A forced line break: `\`. - Linebreak(Linebreak), - /// A paragraph break, indicated by one or multiple blank lines. - Parbreak(Parbreak), - /// An escape sequence: `\#`, `\u{1F5FA}`. - Escape(Escape), - /// A shorthand for a unicode codepoint. For example, `~` for non-breaking - /// space or `-?` for a soft hyphen. - Shorthand(Shorthand), - /// A smart quote: `'` or `"`. - SmartQuote(SmartQuote), - /// Strong content: `*Strong*`. - Strong(Strong), - /// Emphasized content: `_Emphasized_`. - Emph(Emph), - /// Raw text with optional syntax highlighting: `` `...` ``. - Raw(Raw), - /// A hyperlink: `https://typst.org`. - Link(Link), - /// A label: `<intro>`. - Label(Label), - /// A reference: `@target`, `@target[..]`. - Ref(Ref), - /// A section heading: `= Introduction`. - Heading(Heading), - /// An item in a bullet list: `- ...`. - List(ListItem), - /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. - Enum(EnumItem), - /// An item in a term list: `/ Term: Details`. - Term(TermItem), - /// A mathematical equation: `$x$`, `$ x^2 $`. - Equation(Equation), - /// The contents of a mathematical equation: `x^2 + 1`. - Math(Math), - /// An identifier in math: `pi`. - MathIdent(MathIdent), - /// An alignment point in math: `&`. - MathAlignPoint(MathAlignPoint), - /// Matched delimiters in math: `[x + y]`. - MathDelimited(MathDelimited), - /// A base with optional attachments in math: `a_1^2`. - MathAttach(MathAttach), - /// A fraction in math: `x/2`. - MathFrac(MathFrac), - /// A root in math: `√x`, `∛x` or `∜x`. - MathRoot(MathRoot), - /// An identifier: `left`. - Ident(Ident), - /// The `none` literal. - None(None), - /// The `auto` literal. - Auto(Auto), - /// A boolean: `true`, `false`. - Bool(Bool), - /// An integer: `120`. - Int(Int), - /// A floating-point number: `1.2`, `10e-4`. - Float(Float), - /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`. - Numeric(Numeric), - /// A quoted string: `"..."`. - Str(Str), - /// A code block: `{ let x = 1; x + 2 }`. - Code(CodeBlock), - /// A content block: `[*Hi* there!]`. - Content(ContentBlock), - /// A grouped expression: `(1 + 2)`. - Parenthesized(Parenthesized), - /// An array: `(1, "hi", 12cm)`. - Array(Array), - /// A dictionary: `(thickness: 3pt, pattern: dashed)`. - Dict(Dict), - /// A unary operation: `-x`. - Unary(Unary), - /// A binary operation: `a + b`. - Binary(Binary), - /// A field access: `properties.age`. - FieldAccess(FieldAccess), - /// An invocation of a function or method: `f(x, y)`. - FuncCall(FuncCall), - /// A closure: `(x, y) => z`. - Closure(Closure), - /// A let binding: `let x = 1`. - Let(LetBinding), - //// A destructuring assignment: `(x, y) = (1, 2)`. - DestructAssign(DestructAssignment), - /// A set rule: `set text(...)`. - Set(SetRule), - /// A show rule: `show heading: it => emph(it.body)`. - Show(ShowRule), - /// An if-else conditional: `if x { y } else { z }`. - Conditional(Conditional), - /// A while loop: `while x { y }`. - While(WhileLoop), - /// A for loop: `for x in y { z }`. - For(ForLoop), - /// A module import: `import "utils.typ": a, b, c`. - Import(ModuleImport), - /// A module include: `include "chapter1.typ"`. - Include(ModuleInclude), - /// A break from a loop: `break`. - Break(LoopBreak), - /// A continue in a loop: `continue`. - Continue(LoopContinue), - /// A return from a function: `return`, `return x + 1`. - Return(FuncReturn), -} - -impl Expr { - fn cast_with_space(node: &SyntaxNode) -> Option<Self> { - match node.kind() { - SyntaxKind::Space => node.cast().map(Self::Space), - _ => Self::from_untyped(node), - } - } -} - -impl AstNode for Expr { - fn from_untyped(node: &SyntaxNode) -> Option<Self> { - match node.kind() { - SyntaxKind::Linebreak => node.cast().map(Self::Linebreak), - SyntaxKind::Parbreak => node.cast().map(Self::Parbreak), - SyntaxKind::Text => node.cast().map(Self::Text), - SyntaxKind::Escape => node.cast().map(Self::Escape), - SyntaxKind::Shorthand => node.cast().map(Self::Shorthand), - SyntaxKind::SmartQuote => node.cast().map(Self::SmartQuote), - SyntaxKind::Strong => node.cast().map(Self::Strong), - 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), - SyntaxKind::EnumItem => node.cast().map(Self::Enum), - SyntaxKind::TermItem => node.cast().map(Self::Term), - SyntaxKind::Equation => node.cast().map(Self::Equation), - SyntaxKind::Math => node.cast().map(Self::Math), - SyntaxKind::MathIdent => node.cast().map(Self::MathIdent), - SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint), - SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited), - SyntaxKind::MathAttach => node.cast().map(Self::MathAttach), - SyntaxKind::MathFrac => node.cast().map(Self::MathFrac), - SyntaxKind::MathRoot => node.cast().map(Self::MathRoot), - SyntaxKind::Ident => node.cast().map(Self::Ident), - SyntaxKind::None => node.cast().map(Self::None), - SyntaxKind::Auto => node.cast().map(Self::Auto), - SyntaxKind::Bool => node.cast().map(Self::Bool), - SyntaxKind::Int => node.cast().map(Self::Int), - SyntaxKind::Float => node.cast().map(Self::Float), - SyntaxKind::Numeric => node.cast().map(Self::Numeric), - SyntaxKind::Str => node.cast().map(Self::Str), - SyntaxKind::CodeBlock => node.cast().map(Self::Code), - SyntaxKind::ContentBlock => node.cast().map(Self::Content), - SyntaxKind::Parenthesized => node.cast().map(Self::Parenthesized), - SyntaxKind::Array => node.cast().map(Self::Array), - SyntaxKind::Dict => node.cast().map(Self::Dict), - SyntaxKind::Unary => node.cast().map(Self::Unary), - SyntaxKind::Binary => node.cast().map(Self::Binary), - SyntaxKind::FieldAccess => node.cast().map(Self::FieldAccess), - SyntaxKind::FuncCall => node.cast().map(Self::FuncCall), - SyntaxKind::Closure => node.cast().map(Self::Closure), - SyntaxKind::LetBinding => node.cast().map(Self::Let), - SyntaxKind::DestructAssignment => node.cast().map(Self::DestructAssign), - SyntaxKind::SetRule => node.cast().map(Self::Set), - SyntaxKind::ShowRule => node.cast().map(Self::Show), - SyntaxKind::Conditional => node.cast().map(Self::Conditional), - SyntaxKind::WhileLoop => node.cast().map(Self::While), - SyntaxKind::ForLoop => node.cast().map(Self::For), - SyntaxKind::ModuleImport => node.cast().map(Self::Import), - SyntaxKind::ModuleInclude => node.cast().map(Self::Include), - SyntaxKind::LoopBreak => node.cast().map(Self::Break), - SyntaxKind::LoopContinue => node.cast().map(Self::Continue), - SyntaxKind::FuncReturn => node.cast().map(Self::Return), - _ => Option::None, - } - } - - fn as_untyped(&self) -> &SyntaxNode { - match self { - Self::Text(v) => v.as_untyped(), - Self::Space(v) => v.as_untyped(), - Self::Linebreak(v) => v.as_untyped(), - Self::Parbreak(v) => v.as_untyped(), - Self::Escape(v) => v.as_untyped(), - Self::Shorthand(v) => v.as_untyped(), - Self::SmartQuote(v) => v.as_untyped(), - Self::Strong(v) => v.as_untyped(), - 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(), - Self::Enum(v) => v.as_untyped(), - Self::Term(v) => v.as_untyped(), - Self::Equation(v) => v.as_untyped(), - Self::Math(v) => v.as_untyped(), - Self::MathIdent(v) => v.as_untyped(), - Self::MathAlignPoint(v) => v.as_untyped(), - Self::MathDelimited(v) => v.as_untyped(), - Self::MathAttach(v) => v.as_untyped(), - Self::MathFrac(v) => v.as_untyped(), - Self::MathRoot(v) => v.as_untyped(), - Self::Ident(v) => v.as_untyped(), - Self::None(v) => v.as_untyped(), - Self::Auto(v) => v.as_untyped(), - Self::Bool(v) => v.as_untyped(), - Self::Int(v) => v.as_untyped(), - Self::Float(v) => v.as_untyped(), - Self::Numeric(v) => v.as_untyped(), - Self::Str(v) => v.as_untyped(), - Self::Code(v) => v.as_untyped(), - Self::Content(v) => v.as_untyped(), - Self::Array(v) => v.as_untyped(), - Self::Dict(v) => v.as_untyped(), - Self::Parenthesized(v) => v.as_untyped(), - Self::Unary(v) => v.as_untyped(), - Self::Binary(v) => v.as_untyped(), - Self::FieldAccess(v) => v.as_untyped(), - Self::FuncCall(v) => v.as_untyped(), - Self::Closure(v) => v.as_untyped(), - Self::Let(v) => v.as_untyped(), - Self::DestructAssign(v) => v.as_untyped(), - Self::Set(v) => v.as_untyped(), - Self::Show(v) => v.as_untyped(), - Self::Conditional(v) => v.as_untyped(), - Self::While(v) => v.as_untyped(), - Self::For(v) => v.as_untyped(), - Self::Import(v) => v.as_untyped(), - Self::Include(v) => v.as_untyped(), - Self::Break(v) => v.as_untyped(), - Self::Continue(v) => v.as_untyped(), - Self::Return(v) => v.as_untyped(), - } - } -} - -impl Expr { - /// Can this expression be embedded into markup with a hashtag? - pub fn hashtag(&self) -> bool { - matches!( - self, - Self::Ident(_) - | Self::None(_) - | Self::Auto(_) - | Self::Bool(_) - | Self::Int(_) - | Self::Float(_) - | Self::Numeric(_) - | Self::Str(_) - | Self::Code(_) - | Self::Content(_) - | Self::Array(_) - | Self::Dict(_) - | Self::Parenthesized(_) - | Self::FieldAccess(_) - | Self::FuncCall(_) - | Self::Let(_) - | Self::Set(_) - | Self::Show(_) - | Self::Conditional(_) - | Self::While(_) - | Self::For(_) - | Self::Import(_) - | Self::Include(_) - | Self::Break(_) - | Self::Continue(_) - | Self::Return(_) - ) - } - - /// Is this a literal? - pub fn is_literal(&self) -> bool { - matches!( - self, - Self::None(_) - | Self::Auto(_) - | Self::Bool(_) - | Self::Int(_) - | Self::Float(_) - | Self::Numeric(_) - | Self::Str(_) - ) - } -} - -impl Default for Expr { - fn default() -> Self { - Expr::Space(Space::default()) - } -} - -node! { - /// Plain text without markup. - Text -} - -impl Text { - /// Get the text. - pub fn get(&self) -> &EcoString { - self.0.text() - } -} - -node! { - /// Whitespace in markup or math. Has at most one newline in markup, as more - /// indicate a paragraph break. - Space -} - -node! { - /// A forced line break: `\`. - Linebreak -} - -node! { - /// A paragraph break, indicated by one or multiple blank lines. - Parbreak -} - -node! { - /// An escape sequence: `\#`, `\u{1F5FA}`. - Escape -} - -impl Escape { - /// Get the escaped character. - pub fn get(&self) -> char { - let mut s = Scanner::new(self.0.text()); - s.expect('\\'); - if s.eat_if("u{") { - let hex = s.eat_while(char::is_ascii_hexdigit); - u32::from_str_radix(hex, 16) - .ok() - .and_then(std::char::from_u32) - .unwrap_or_default() - } else { - s.eat().unwrap_or_default() - } - } -} - -node! { - /// A shorthand for a unicode codepoint. For example, `~` for a non-breaking - /// space or `-?` for a soft hyphen. - Shorthand -} - -impl Shorthand { - /// A list of all shorthands. - pub const LIST: &[(&'static str, char)] = &[ - // Both. - ("...", '…'), - // Text only. - ("~", '\u{00A0}'), - ("--", '\u{2013}'), - ("---", '\u{2014}'), - ("-?", '\u{00AD}'), - // Math only. - ("-", '\u{2212}'), - ("'", '′'), - ("*", '∗'), - ("!=", '≠'), - (":=", '≔'), - ("::=", '⩴'), - ("=:", '≕'), - ("<<", '≪'), - ("<<<", '⋘'), - (">>", '≫'), - (">>>", '⋙'), - ("<=", '≤'), - (">=", '≥'), - ("->", '→'), - ("-->", '⟶'), - ("|->", '↦'), - (">->", '↣'), - ("->>", '↠'), - ("<-", '←'), - ("<--", '⟵'), - ("<-<", '↢'), - ("<<-", '↞'), - ("<->", '↔'), - ("<-->", '⟷'), - ("~>", '⇝'), - ("~~>", '⟿'), - ("<~", '⇜'), - ("<~~", '⬳'), - ("=>", '⇒'), - ("|=>", '⤇'), - ("==>", '⟹'), - ("<==", '⟸'), - ("<=>", '⇔'), - ("<==>", '⟺'), - ("[|", '⟦'), - ("|]", '⟧'), - ("||", '‖'), - ]; - - /// Get the shorthanded character. - pub fn get(&self) -> char { - let text = self.0.text(); - Self::LIST - .iter() - .find(|&&(s, _)| s == text) - .map_or_else(char::default, |&(_, c)| c) - } -} - -node! { - /// A smart quote: `'` or `"`. - SmartQuote -} - -impl SmartQuote { - /// Whether this is a double quote. - pub fn double(&self) -> bool { - self.0.text() == "\"" - } -} - -node! { - /// Strong content: `*Strong*`. - Strong -} - -impl Strong { - /// The contents of the strong node. - pub fn body(&self) -> Markup { - self.0.cast_first_match().unwrap_or_default() - } -} - -node! { - /// Emphasized content: `_Emphasized_`. - Emph -} - -impl Emph { - /// The contents of the emphasis node. - pub fn body(&self) -> Markup { - self.0.cast_first_match().unwrap_or_default() - } -} - -node! { - /// Raw text with optional syntax highlighting: `` `...` ``. - Raw -} - -impl Raw { - /// The trimmed raw text. - pub fn text(&self) -> EcoString { - let mut text = self.0.text().as_str(); - let blocky = text.starts_with("```"); - text = text.trim_matches('`'); - - // Trim tag, one space at the start, and one space at the end if the - // last non-whitespace char is a backtick. - if blocky { - let mut s = Scanner::new(text); - if s.eat_if(is_id_start) { - s.eat_while(is_id_continue); - } - text = s.after(); - text = text.strip_prefix(' ').unwrap_or(text); - if text.trim_end().ends_with('`') { - text = text.strip_suffix(' ').unwrap_or(text); - } - } - - // Split into lines. - let mut lines = split_newlines(text); - - if blocky { - let dedent = lines - .iter() - .skip(1) - .map(|line| line.chars().take_while(|c| c.is_whitespace()).count()) - .min() - .unwrap_or(0); - - // Dedent based on column, but not for the first line. - for line in lines.iter_mut().skip(1) { - let offset = line.chars().take(dedent).map(char::len_utf8).sum(); - *line = &line[offset..]; - } - - let is_whitespace = |line: &&str| line.chars().all(char::is_whitespace); - - // Trims a sequence of whitespace followed by a newline at the start. - if lines.first().map_or(false, is_whitespace) { - lines.remove(0); - } - - // Trims a newline followed by a sequence of whitespace at the end. - if lines.last().map_or(false, is_whitespace) { - lines.pop(); - } - } - - lines.join("\n").into() - } - - /// An optional identifier specifying the language to syntax-highlight in. - pub fn lang(&self) -> Option<&str> { - let text = self.0.text(); - - // Only blocky literals are supposed to contain a language. - if !text.starts_with("```") { - return Option::None; - } - - let inner = text.trim_start_matches('`'); - let mut s = Scanner::new(inner); - s.eat_if(is_id_start).then(|| { - s.eat_while(is_id_continue); - s.before() - }) - } - - /// Whether the raw text should be displayed in a separate block. - pub fn block(&self) -> bool { - let text = self.0.text(); - text.starts_with("```") && text.chars().any(is_newline) - } -} - -node! { - /// A hyperlink: `https://typst.org`. - Link -} - -impl Link { - /// Get the URL. - pub fn get(&self) -> &EcoString { - self.0.text() - } -} - -node! { - /// A label: `<intro>`. - Label -} - -impl Label { - /// Get the label's text. - pub fn get(&self) -> &str { - self.0.text().trim_start_matches('<').trim_end_matches('>') - } -} - -node! { - /// A reference: `@target`, `@target[..]`. - Ref -} - -impl Ref { - /// Get the target. - pub fn target(&self) -> &str { - self.0 - .children() - .find(|node| node.kind() == SyntaxKind::RefMarker) - .map(|node| node.text().trim_start_matches('@')) - .unwrap_or_default() - } - - /// Get the supplement. - pub fn supplement(&self) -> Option<ContentBlock> { - self.0.cast_last_match() - } -} - -node! { - /// A section heading: `= Introduction`. - Heading -} - -impl Heading { - /// The contents of the heading. - pub fn body(&self) -> Markup { - self.0.cast_first_match().unwrap_or_default() - } - - /// The section depth (number of equals signs). - pub fn level(&self) -> NonZeroUsize { - self.0 - .children() - .find(|node| node.kind() == SyntaxKind::HeadingMarker) - .and_then(|node| node.len().try_into().ok()) - .unwrap_or(NonZeroUsize::ONE) - } -} - -node! { - /// An item in a bullet list: `- ...`. - ListItem -} - -impl ListItem { - /// The contents of the list item. - pub fn body(&self) -> Markup { - self.0.cast_first_match().unwrap_or_default() - } -} - -node! { - /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. - EnumItem -} - -impl EnumItem { - /// The explicit numbering, if any: `23.`. - pub fn number(&self) -> Option<usize> { - self.0.children().find_map(|node| match node.kind() { - SyntaxKind::EnumMarker => node.text().trim_end_matches('.').parse().ok(), - _ => Option::None, - }) - } - - /// The contents of the list item. - pub fn body(&self) -> Markup { - self.0.cast_first_match().unwrap_or_default() - } -} - -node! { - /// An item in a term list: `/ Term: Details`. - TermItem -} - -impl TermItem { - /// The term described by the item. - pub fn term(&self) -> Markup { - self.0.cast_first_match().unwrap_or_default() - } - - /// The description of the term. - pub fn description(&self) -> Markup { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A mathemathical equation: `$x$`, `$ x^2 $`. - Equation -} - -impl Equation { - /// The contained math. - pub fn body(&self) -> Math { - self.0.cast_first_match().unwrap_or_default() - } - - /// Whether the equation should be displayed as a separate block. - pub fn block(&self) -> bool { - let is_space = |node: Option<&SyntaxNode>| { - node.map(SyntaxNode::kind) == Some(SyntaxKind::Space) - }; - is_space(self.0.children().nth(1)) && is_space(self.0.children().nth_back(1)) - } -} - -node! { - /// The contents of a mathematical equation: `x^2 + 1`. - Math -} - -impl Math { - /// The expressions the mathematical content consists of. - pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ { - self.0.children().filter_map(Expr::cast_with_space) - } -} - -node! { - /// An identifier in math: `pi`. - MathIdent -} - -impl MathIdent { - /// Get the identifier. - pub fn get(&self) -> &EcoString { - self.0.text() - } - - /// Take out the contained identifier. - pub fn take(self) -> EcoString { - self.0.into_text() - } - - /// Get the identifier as a string slice. - pub fn as_str(&self) -> &str { - self.get() - } -} - -impl Deref for MathIdent { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.as_str() - } -} - -node! { - /// An alignment point in math: `&`. - MathAlignPoint -} - -node! { - /// Matched delimiters in math: `[x + y]`. - MathDelimited -} - -impl MathDelimited { - /// The opening delimiter. - pub fn open(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The contents, including the delimiters. - pub fn body(&self) -> Math { - self.0.cast_first_match().unwrap_or_default() - } - - /// The closing delimiter. - pub fn close(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A base with optional attachments in math: `a_1^2`. - MathAttach -} - -impl MathAttach { - /// The base, to which things are attached. - pub fn base(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The bottom attachment. - pub fn bottom(&self) -> Option<Expr> { - self.0 - .children() - .skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore)) - .find_map(SyntaxNode::cast) - } - - /// The top attachment. - pub fn top(&self) -> Option<Expr> { - self.0 - .children() - .skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat)) - .find_map(SyntaxNode::cast) - } -} - -node! { - /// A fraction in math: `x/2` - MathFrac -} - -impl MathFrac { - /// The numerator. - pub fn num(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The denominator. - pub fn denom(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A root in math: `√x`, `∛x` or `∜x`. - MathRoot -} - -impl MathRoot { - /// The index of the root. - pub fn index(&self) -> Option<usize> { - match self.0.children().next().map(|node| node.text().as_str()) { - Some("∜") => Some(4), - Some("∛") => Some(3), - Some("√") => Option::None, - _ => Option::None, - } - } - - /// The radicand. - pub fn radicand(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } -} - -node! { - /// An identifier: `it`. - Ident -} - -impl Ident { - /// Get the identifier. - pub fn get(&self) -> &EcoString { - self.0.text() - } - - /// Take out the contained identifier. - pub fn take(self) -> EcoString { - self.0.into_text() - } - - /// Get the identifier as a string slice. - pub fn as_str(&self) -> &str { - self.get() - } -} - -impl Deref for Ident { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.as_str() - } -} - -node! { - /// The `none` literal. - None -} - -node! { - /// The `auto` literal. - Auto -} - -node! { - /// A boolean: `true`, `false`. - Bool -} - -impl Bool { - /// Get the boolean value. - pub fn get(&self) -> bool { - self.0.text() == "true" - } -} - -node! { - /// An integer: `120`. - Int -} - -impl Int { - /// Get the integer value. - pub fn get(&self) -> i64 { - let text = self.0.text(); - if let Some(rest) = text.strip_prefix("0x") { - i64::from_str_radix(rest, 16) - } else if let Some(rest) = text.strip_prefix("0o") { - i64::from_str_radix(rest, 8) - } else if let Some(rest) = text.strip_prefix("0b") { - i64::from_str_radix(rest, 2) - } else { - text.parse() - } - .unwrap_or_default() - } -} - -node! { - /// A floating-point number: `1.2`, `10e-4`. - Float -} - -impl Float { - /// Get the floating-point value. - pub fn get(&self) -> f64 { - self.0.text().parse().unwrap_or_default() - } -} - -node! { - /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`. - Numeric -} - -impl Numeric { - /// Get the numeric value and unit. - pub fn get(&self) -> (f64, Unit) { - let text = self.0.text(); - let count = text - .chars() - .rev() - .take_while(|c| matches!(c, 'a'..='z' | '%')) - .count(); - - let split = text.len() - count; - let value = text[..split].parse().unwrap_or_default(); - let unit = match &text[split..] { - "pt" => Unit::Length(AbsUnit::Pt), - "mm" => Unit::Length(AbsUnit::Mm), - "cm" => Unit::Length(AbsUnit::Cm), - "in" => Unit::Length(AbsUnit::In), - "deg" => Unit::Angle(AngleUnit::Deg), - "rad" => Unit::Angle(AngleUnit::Rad), - "em" => Unit::Em, - "fr" => Unit::Fr, - "%" => Unit::Percent, - _ => Unit::Percent, - }; - - (value, unit) - } -} - -/// Unit of a numeric value. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Unit { - /// An absolute length unit. - Length(AbsUnit), - /// An angular unit. - Angle(AngleUnit), - /// Font-relative: `1em` is the same as the font size. - Em, - /// Fractions: `fr`. - Fr, - /// Percentage: `%`. - Percent, -} - -node! { - /// A quoted string: `"..."`. - Str -} - -impl Str { - /// Get the string value with resolved escape sequences. - pub fn get(&self) -> EcoString { - let text = self.0.text(); - let unquoted = &text[1..text.len() - 1]; - if !unquoted.contains('\\') { - return unquoted.into(); - } - - let mut out = EcoString::with_capacity(unquoted.len()); - let mut s = Scanner::new(unquoted); - - while let Some(c) = s.eat() { - if c != '\\' { - out.push(c); - continue; - } - - let start = s.locate(-1); - match s.eat() { - Some('\\') => out.push('\\'), - Some('"') => out.push('"'), - Some('n') => out.push('\n'), - Some('r') => out.push('\r'), - Some('t') => out.push('\t'), - Some('u') if s.eat_if('{') => { - let sequence = s.eat_while(char::is_ascii_hexdigit); - s.eat_if('}'); - - match u32::from_str_radix(sequence, 16) - .ok() - .and_then(std::char::from_u32) - { - Some(c) => out.push(c), - Option::None => out.push_str(s.from(start)), - } - } - _ => out.push_str(s.from(start)), - } - } - - out - } -} - -node! { - /// A code block: `{ let x = 1; x + 2 }`. - CodeBlock -} - -impl CodeBlock { - /// The contained code. - pub fn body(&self) -> Code { - self.0.cast_first_match().unwrap_or_default() - } -} - -node! { - /// Code. - Code -} - -impl Code { - /// The list of expressions contained in the code. - pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ { - self.0.children().filter_map(SyntaxNode::cast) - } -} - -node! { - /// A content block: `[*Hi* there!]`. - ContentBlock -} - -impl ContentBlock { - /// The contained markup. - pub fn body(&self) -> Markup { - self.0.cast_first_match().unwrap_or_default() - } -} - -node! { - /// A grouped expression: `(1 + 2)`. - Parenthesized -} - -impl Parenthesized { - /// The wrapped expression. - pub fn expr(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } -} - -node! { - /// An array: `(1, "hi", 12cm)`. - Array -} - -impl Array { - /// The array's items. - pub fn items(&self) -> impl DoubleEndedIterator<Item = ArrayItem> + '_ { - self.0.children().filter_map(SyntaxNode::cast) - } -} - -/// An item in an array. -#[derive(Debug, Clone, Hash)] -pub enum ArrayItem { - /// A bare expression: `12`. - Pos(Expr), - /// A spread expression: `..things`. - Spread(Expr), -} - -impl AstNode for ArrayItem { - fn from_untyped(node: &SyntaxNode) -> Option<Self> { - match node.kind() { - SyntaxKind::Spread => node.cast_first_match().map(Self::Spread), - _ => node.cast().map(Self::Pos), - } - } - - fn as_untyped(&self) -> &SyntaxNode { - match self { - Self::Pos(v) => v.as_untyped(), - Self::Spread(v) => v.as_untyped(), - } - } -} - -node! { - /// A dictionary: `(thickness: 3pt, pattern: dashed)`. - Dict -} - -impl Dict { - /// The dictionary's items. - pub fn items(&self) -> impl DoubleEndedIterator<Item = DictItem> + '_ { - self.0.children().filter_map(SyntaxNode::cast) - } -} - -/// An item in an dictionary expression. -#[derive(Debug, Clone, Hash)] -pub enum DictItem { - /// A named pair: `thickness: 3pt`. - Named(Named), - /// A keyed pair: `"spacy key": true`. - Keyed(Keyed), - /// A spread expression: `..things`. - Spread(Expr), -} - -impl AstNode for DictItem { - fn from_untyped(node: &SyntaxNode) -> Option<Self> { - match node.kind() { - SyntaxKind::Named => node.cast().map(Self::Named), - SyntaxKind::Keyed => node.cast().map(Self::Keyed), - SyntaxKind::Spread => node.cast_first_match().map(Self::Spread), - _ => Option::None, - } - } - - fn as_untyped(&self) -> &SyntaxNode { - match self { - Self::Named(v) => v.as_untyped(), - Self::Keyed(v) => v.as_untyped(), - Self::Spread(v) => v.as_untyped(), - } - } -} - -node! { - /// A named pair: `thickness: 3pt`. - Named -} - -impl Named { - /// The name: `thickness`. - pub fn name(&self) -> Ident { - self.0.cast_first_match().unwrap_or_default() - } - - /// The right-hand side of the pair: `3pt`. - pub fn expr(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } - - /// The right-hand side of the pair as an identifier. - pub fn expr_ident(&self) -> Option<Ident> { - self.0.cast_last_match() - } -} - -node! { - /// A keyed pair: `"spacy key": true`. - Keyed -} - -impl Keyed { - /// The key: `"spacy key"`. - pub fn key(&self) -> Str { - self.0 - .children() - .find_map(|node| node.cast::<Str>()) - .unwrap_or_default() - } - - /// The right-hand side of the pair: `true`. - pub fn expr(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A unary operation: `-x`. - Unary -} - -impl Unary { - /// The operator: `-`. - pub fn op(&self) -> UnOp { - self.0 - .children() - .find_map(|node| UnOp::from_kind(node.kind())) - .unwrap_or(UnOp::Pos) - } - - /// The expression to operate on: `x`. - pub fn expr(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -/// A unary operator. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum UnOp { - /// The plus operator: `+`. - Pos, - /// The negation operator: `-`. - Neg, - /// The boolean `not`. - Not, -} - -impl UnOp { - /// Try to convert the token into a unary operation. - pub fn from_kind(token: SyntaxKind) -> Option<Self> { - Some(match token { - SyntaxKind::Plus => Self::Pos, - SyntaxKind::Minus => Self::Neg, - SyntaxKind::Not => Self::Not, - _ => return Option::None, - }) - } - - /// The precedence of this operator. - pub fn precedence(self) -> usize { - match self { - Self::Pos | Self::Neg => 7, - Self::Not => 4, - } - } - - /// The string representation of this operation. - pub fn as_str(self) -> &'static str { - match self { - Self::Pos => "+", - Self::Neg => "-", - Self::Not => "not", - } - } -} - -node! { - /// A binary operation: `a + b`. - Binary -} - -impl Binary { - /// The binary operator: `+`. - pub fn op(&self) -> BinOp { - let mut not = false; - self.0 - .children() - .find_map(|node| match node.kind() { - SyntaxKind::Not => { - not = true; - Option::None - } - SyntaxKind::In if not => Some(BinOp::NotIn), - _ => BinOp::from_kind(node.kind()), - }) - .unwrap_or(BinOp::Add) - } - - /// The left-hand side of the operation: `a`. - pub fn lhs(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The right-hand side of the operation: `b`. - pub fn rhs(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -/// A binary operator. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum BinOp { - /// The addition operator: `+`. - Add, - /// The subtraction operator: `-`. - Sub, - /// The multiplication operator: `*`. - Mul, - /// The division operator: `/`. - Div, - /// The short-circuiting boolean `and`. - And, - /// The short-circuiting boolean `or`. - Or, - /// The equality operator: `==`. - Eq, - /// The inequality operator: `!=`. - Neq, - /// The less-than operator: `<`. - Lt, - /// The less-than or equal operator: `<=`. - Leq, - /// The greater-than operator: `>`. - Gt, - /// The greater-than or equal operator: `>=`. - Geq, - /// The assignment operator: `=`. - Assign, - /// The containment operator: `in`. - In, - /// The inversed containment operator: `not in`. - NotIn, - /// The add-assign operator: `+=`. - AddAssign, - /// The subtract-assign oeprator: `-=`. - SubAssign, - /// The multiply-assign operator: `*=`. - MulAssign, - /// The divide-assign operator: `/=`. - DivAssign, -} - -impl BinOp { - /// Try to convert the token into a binary operation. - pub fn from_kind(token: SyntaxKind) -> Option<Self> { - Some(match token { - SyntaxKind::Plus => Self::Add, - SyntaxKind::Minus => Self::Sub, - SyntaxKind::Star => Self::Mul, - SyntaxKind::Slash => Self::Div, - SyntaxKind::And => Self::And, - SyntaxKind::Or => Self::Or, - SyntaxKind::EqEq => Self::Eq, - SyntaxKind::ExclEq => Self::Neq, - SyntaxKind::Lt => Self::Lt, - SyntaxKind::LtEq => Self::Leq, - SyntaxKind::Gt => Self::Gt, - SyntaxKind::GtEq => Self::Geq, - SyntaxKind::Eq => Self::Assign, - SyntaxKind::In => Self::In, - SyntaxKind::PlusEq => Self::AddAssign, - SyntaxKind::HyphEq => Self::SubAssign, - SyntaxKind::StarEq => Self::MulAssign, - SyntaxKind::SlashEq => Self::DivAssign, - _ => return Option::None, - }) - } - - /// The precedence of this operator. - pub fn precedence(self) -> usize { - match self { - Self::Mul => 6, - Self::Div => 6, - Self::Add => 5, - Self::Sub => 5, - Self::Eq => 4, - Self::Neq => 4, - Self::Lt => 4, - Self::Leq => 4, - Self::Gt => 4, - Self::Geq => 4, - Self::In => 4, - Self::NotIn => 4, - Self::And => 3, - Self::Or => 2, - Self::Assign => 1, - Self::AddAssign => 1, - Self::SubAssign => 1, - Self::MulAssign => 1, - Self::DivAssign => 1, - } - } - - /// The associativity of this operator. - pub fn assoc(self) -> Assoc { - match self { - Self::Add => Assoc::Left, - Self::Sub => Assoc::Left, - Self::Mul => Assoc::Left, - Self::Div => Assoc::Left, - Self::And => Assoc::Left, - Self::Or => Assoc::Left, - Self::Eq => Assoc::Left, - Self::Neq => Assoc::Left, - Self::Lt => Assoc::Left, - Self::Leq => Assoc::Left, - Self::Gt => Assoc::Left, - Self::Geq => Assoc::Left, - Self::In => Assoc::Left, - Self::NotIn => Assoc::Left, - Self::Assign => Assoc::Right, - Self::AddAssign => Assoc::Right, - Self::SubAssign => Assoc::Right, - Self::MulAssign => Assoc::Right, - Self::DivAssign => Assoc::Right, - } - } - - /// The string representation of this operation. - pub fn as_str(self) -> &'static str { - match self { - Self::Add => "+", - Self::Sub => "-", - Self::Mul => "*", - Self::Div => "/", - Self::And => "and", - Self::Or => "or", - Self::Eq => "==", - Self::Neq => "!=", - Self::Lt => "<", - Self::Leq => "<=", - Self::Gt => ">", - Self::Geq => ">=", - Self::In => "in", - Self::NotIn => "not in", - Self::Assign => "=", - Self::AddAssign => "+=", - Self::SubAssign => "-=", - Self::MulAssign => "*=", - Self::DivAssign => "/=", - } - } -} - -/// The associativity of a binary operator. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Assoc { - /// Left-associative: `a + b + c` is equivalent to `(a + b) + c`. - Left, - /// Right-associative: `a = b = c` is equivalent to `a = (b = c)`. - Right, -} - -node! { - /// A field access: `properties.age`. - FieldAccess -} - -impl FieldAccess { - /// The expression to access the field on. - pub fn target(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The name of the field. - pub fn field(&self) -> Ident { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// An invocation of a function or method: `f(x, y)`. - FuncCall -} - -impl FuncCall { - /// The function to call. - pub fn callee(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The arguments to the function. - pub fn args(&self) -> Args { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A function call's argument list: `(12pt, y)`. - Args -} - -impl Args { - /// The positional and named arguments. - pub fn items(&self) -> impl DoubleEndedIterator<Item = Arg> + '_ { - self.0.children().filter_map(SyntaxNode::cast) - } -} - -/// An argument to a function call. -#[derive(Debug, Clone, Hash)] -pub enum Arg { - /// A positional argument: `12`. - Pos(Expr), - /// A named argument: `draw: false`. - Named(Named), - /// A spread argument: `..things`. - Spread(Expr), -} - -impl AstNode for Arg { - fn from_untyped(node: &SyntaxNode) -> Option<Self> { - match node.kind() { - SyntaxKind::Named => node.cast().map(Self::Named), - SyntaxKind::Spread => node.cast_first_match().map(Self::Spread), - _ => node.cast().map(Self::Pos), - } - } - - fn as_untyped(&self) -> &SyntaxNode { - match self { - Self::Pos(v) => v.as_untyped(), - Self::Named(v) => v.as_untyped(), - Self::Spread(v) => v.as_untyped(), - } - } -} - -node! { - /// A closure: `(x, y) => z`. - Closure -} - -impl Closure { - /// The name of the closure. - /// - /// This only exists if you use the function syntax sugar: `let f(x) = y`. - pub fn name(&self) -> Option<Ident> { - self.0.children().next()?.cast() - } - - /// The parameter bindings. - pub fn params(&self) -> Params { - self.0.cast_first_match().unwrap_or_default() - } - - /// The body of the closure. - pub fn body(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A closure's parameters: `(x, y)`. - Params -} - -impl Params { - /// The parameter bindings. - pub fn children(&self) -> impl DoubleEndedIterator<Item = Param> + '_ { - self.0.children().filter_map(SyntaxNode::cast) - } -} - -node! { - /// A spread: `..x` or `..x.at(0)`. - Spread -} - -impl Spread { - /// Try to get an identifier. - pub fn name(&self) -> Option<Ident> { - self.0.cast_first_match() - } - - /// Try to get an expression. - pub fn expr(&self) -> Option<Expr> { - self.0.cast_first_match() - } -} - -node! { - /// An underscore: `_` - Underscore -} - -/// A parameter to a closure. -#[derive(Debug, Clone, Hash)] -pub enum Param { - /// A positional parameter: `x`. - Pos(Pattern), - /// A named parameter with a default value: `draw: false`. - Named(Named), - /// An argument sink: `..args`. - Sink(Spread), -} - -impl AstNode for Param { - fn from_untyped(node: &SyntaxNode) -> Option<Self> { - match node.kind() { - SyntaxKind::Named => node.cast().map(Self::Named), - SyntaxKind::Spread => node.cast().map(Self::Sink), - _ => node.cast().map(Self::Pos), - } - } - - fn as_untyped(&self) -> &SyntaxNode { - match self { - Self::Pos(v) => v.as_untyped(), - Self::Named(v) => v.as_untyped(), - Self::Sink(v) => v.as_untyped(), - } - } -} - -node! { - /// A destructuring pattern: `x` or `(x, _, ..y)`. - Destructuring -} - -impl Destructuring { - /// The bindings of the destructuring. - pub fn bindings(&self) -> impl Iterator<Item = DestructuringKind> + '_ { - self.0.children().filter_map(SyntaxNode::cast) - } - - // Returns a list of all identifiers in the pattern. - pub fn idents(&self) -> impl Iterator<Item = Ident> + '_ { - self.bindings().filter_map(|binding| match binding { - DestructuringKind::Normal(Expr::Ident(ident)) => Some(ident), - DestructuringKind::Sink(spread) => spread.name(), - DestructuringKind::Named(named) => named.expr_ident(), - _ => Option::None, - }) - } -} - -/// The kind of an element in a destructuring pattern. -#[derive(Debug, Clone, Hash)] -pub enum DestructuringKind { - /// An expression: `x`. - Normal(Expr), - /// An argument sink: `..y`. - Sink(Spread), - /// Named arguments: `x: 1`. - Named(Named), - /// A placeholder: `_`. - Placeholder(Underscore), -} - -impl AstNode for DestructuringKind { - fn from_untyped(node: &SyntaxNode) -> Option<Self> { - match node.kind() { - SyntaxKind::Named => node.cast().map(Self::Named), - SyntaxKind::Spread => node.cast().map(Self::Sink), - SyntaxKind::Underscore => node.cast().map(Self::Placeholder), - _ => node.cast().map(Self::Normal), - } - } - - fn as_untyped(&self) -> &SyntaxNode { - match self { - Self::Normal(v) => v.as_untyped(), - Self::Named(v) => v.as_untyped(), - Self::Sink(v) => v.as_untyped(), - Self::Placeholder(v) => v.as_untyped(), - } - } -} - -/// The kind of a pattern. -#[derive(Debug, Clone, Hash)] -pub enum Pattern { - /// A single expression: `x`. - Normal(Expr), - /// A placeholder: `_`. - Placeholder(Underscore), - /// A destructuring pattern: `(x, _, ..y)`. - Destructuring(Destructuring), -} - -impl AstNode for Pattern { - fn from_untyped(node: &SyntaxNode) -> Option<Self> { - match node.kind() { - SyntaxKind::Destructuring => node.cast().map(Self::Destructuring), - SyntaxKind::Underscore => node.cast().map(Self::Placeholder), - _ => node.cast().map(Self::Normal), - } - } - - fn as_untyped(&self) -> &SyntaxNode { - match self { - Self::Normal(v) => v.as_untyped(), - Self::Destructuring(v) => v.as_untyped(), - Self::Placeholder(v) => v.as_untyped(), - } - } -} - -impl Pattern { - // Returns a list of all identifiers in the pattern. - pub fn idents(&self) -> Vec<Ident> { - match self { - Pattern::Normal(Expr::Ident(ident)) => vec![ident.clone()], - Pattern::Destructuring(destruct) => destruct.idents().collect(), - _ => vec![], - } - } -} - -impl Default for Pattern { - fn default() -> Self { - Self::Normal(Expr::default()) - } -} - -node! { - /// A let binding: `let x = 1`. - LetBinding -} - -#[derive(Debug)] -pub enum LetBindingKind { - /// A normal binding: `let x = 1`. - Normal(Pattern), - /// A closure binding: `let f(x) = 1`. - Closure(Ident), -} - -impl LetBindingKind { - // Returns a list of all identifiers in the pattern. - pub fn idents(&self) -> Vec<Ident> { - match self { - LetBindingKind::Normal(pattern) => pattern.idents(), - LetBindingKind::Closure(ident) => { - vec![ident.clone()] - } - } - } -} - -impl LetBinding { - /// The kind of the let binding. - pub fn kind(&self) -> LetBindingKind { - match self.0.cast_first_match::<Pattern>() { - Some(Pattern::Normal(Expr::Closure(closure))) => { - LetBindingKind::Closure(closure.name().unwrap_or_default()) - } - pattern => LetBindingKind::Normal(pattern.unwrap_or_default()), - } - } - - /// The expression the binding is initialized with. - pub fn init(&self) -> Option<Expr> { - match self.kind() { - LetBindingKind::Normal(Pattern::Normal(_)) => { - self.0.children().filter_map(SyntaxNode::cast).nth(1) - } - LetBindingKind::Normal(_) => self.0.cast_first_match(), - LetBindingKind::Closure(_) => self.0.cast_first_match(), - } - } -} - -node! { - /// An assignment expression `(x, y) = (1, 2)`. - DestructAssignment -} - -impl DestructAssignment { - /// The pattern of the assignment. - pub fn pattern(&self) -> Pattern { - self.0.cast_first_match::<Pattern>().unwrap_or_default() - } - - /// The expression that is assigned. - pub fn value(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A set rule: `set text(...)`. - SetRule -} - -impl SetRule { - /// The function to set style properties for. - pub fn target(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The style properties to set. - pub fn args(&self) -> Args { - self.0.cast_last_match().unwrap_or_default() - } - - /// A condition under which the set rule applies. - pub fn condition(&self) -> Option<Expr> { - self.0 - .children() - .skip_while(|child| child.kind() != SyntaxKind::If) - .find_map(SyntaxNode::cast) - } -} - -node! { - /// A show rule: `show heading: it => emph(it.body)`. - ShowRule -} - -impl ShowRule { - /// Defines which nodes the show rule applies to. - pub fn selector(&self) -> Option<Expr> { - self.0 - .children() - .rev() - .skip_while(|child| child.kind() != SyntaxKind::Colon) - .find_map(SyntaxNode::cast) - } - - /// The transformation recipe. - pub fn transform(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// An if-else conditional: `if x { y } else { z }`. - Conditional -} - -impl Conditional { - /// The condition which selects the body to evaluate. - pub fn condition(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The expression to evaluate if the condition is true. - pub fn if_body(&self) -> Expr { - self.0 - .children() - .filter_map(SyntaxNode::cast) - .nth(1) - .unwrap_or_default() - } - - /// The expression to evaluate if the condition is false. - pub fn else_body(&self) -> Option<Expr> { - self.0.children().filter_map(SyntaxNode::cast).nth(2) - } -} - -node! { - /// A while loop: `while x { y }`. - WhileLoop -} - -impl WhileLoop { - /// The condition which selects whether to evaluate the body. - pub fn condition(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The expression to evaluate while the condition is true. - pub fn body(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A for loop: `for x in y { z }`. - ForLoop -} - -impl ForLoop { - /// The pattern to assign to. - pub fn pattern(&self) -> Pattern { - self.0.cast_first_match().unwrap_or_default() - } - - /// The expression to iterate over. - pub fn iter(&self) -> Expr { - self.0 - .children() - .skip_while(|&c| c.kind() != SyntaxKind::In) - .find_map(SyntaxNode::cast) - .unwrap_or_default() - } - - /// The expression to evaluate for each iteration. - pub fn body(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A module import: `import "utils.typ": a, b, c`. - ModuleImport -} - -impl ModuleImport { - /// The module or path from which the items should be imported. - pub fn source(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The items to be imported. - pub fn imports(&self) -> Option<Imports> { - self.0.children().find_map(|node| match node.kind() { - SyntaxKind::Star => Some(Imports::Wildcard), - SyntaxKind::ImportItems => { - let items = node.children().filter_map(SyntaxNode::cast).collect(); - Some(Imports::Items(items)) - } - _ => Option::None, - }) - } -} - -/// The items that ought to be imported from a file. -#[derive(Debug, Clone, Hash)] -pub enum Imports { - /// All items in the scope of the file should be imported. - Wildcard, - /// The specified items from the file should be imported. - Items(Vec<Ident>), -} - -node! { - /// A module include: `include "chapter1.typ"`. - ModuleInclude -} - -impl ModuleInclude { - /// The module or path from which the content should be included. - pub fn source(&self) -> Expr { - self.0.cast_last_match().unwrap_or_default() - } -} - -node! { - /// A break from a loop: `break`. - LoopBreak -} - -node! { - /// A continue in a loop: `continue`. - LoopContinue -} - -node! { - /// A return from a function: `return`, `return x + 1`. - FuncReturn -} - -impl FuncReturn { - /// The expression to return. - pub fn body(&self) -> Option<Expr> { - self.0.cast_last_match() - } -} diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs deleted file mode 100644 index 26e949ca..00000000 --- a/src/syntax/kind.rs +++ /dev/null @@ -1,448 +0,0 @@ -/// A syntactical building block of a Typst file. -/// -/// Can be created by the lexer or by the parser. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -#[repr(u8)] -pub enum SyntaxKind { - /// Markup. - Markup, - /// Plain text without markup. - Text, - /// Whitespace. Contains at most one newline in markup, as more indicate a - /// paragraph break. - Space, - /// A forced line break: `\`. - Linebreak, - /// A paragraph break, indicated by one or multiple blank lines. - Parbreak, - /// An escape sequence: `\#`, `\u{1F5FA}`. - Escape, - /// A shorthand for a unicode codepoint. For example, `~` for non-breaking - /// space or `-?` for a soft hyphen. - Shorthand, - /// A smart quote: `'` or `"`. - SmartQuote, - /// Strong content: `*Strong*`. - Strong, - /// Emphasized content: `_Emphasized_`. - Emph, - /// Raw text with optional syntax highlighting: `` `...` ``. - Raw, - /// A hyperlink: `https://typst.org`. - Link, - /// A label: `<intro>`. - Label, - /// A reference: `@target`, `@target[..]`. - Ref, - /// Introduces a reference: `@target`. - RefMarker, - /// A section heading: `= Introduction`. - Heading, - /// Introduces a section heading: `=`, `==`, ... - HeadingMarker, - /// An item in a bullet list: `- ...`. - ListItem, - /// Introduces a list item: `-`. - ListMarker, - /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. - EnumItem, - /// Introduces an enumeration item: `+`, `1.`. - EnumMarker, - /// An item in a term list: `/ Term: Details`. - TermItem, - /// Introduces a term item: `/`. - TermMarker, - /// A mathematical equation: `$x$`, `$ x^2 $`. - Equation, - - /// The contents of a mathematical equation: `x^2 + 1`. - Math, - /// An identifier in math: `pi`. - MathIdent, - /// An alignment point in math: `&`. - MathAlignPoint, - /// Matched delimiters in math: `[x + y]`. - MathDelimited, - /// A base with optional attachments in math: `a_1^2`. - MathAttach, - /// A fraction in math: `x/2`. - MathFrac, - /// A root in math: `√x`, `∛x` or `∜x`. - MathRoot, - - /// A hashtag that switches into code mode: `#`. - Hashtag, - /// A left curly brace, starting a code block: `{`. - LeftBrace, - /// A right curly brace, terminating a code block: `}`. - RightBrace, - /// A left square bracket, starting a content block: `[`. - LeftBracket, - /// A right square bracket, terminating a content block: `]`. - RightBracket, - /// A left round parenthesis, starting a grouped expression, collection, - /// argument or parameter list: `(`. - LeftParen, - /// A right round parenthesis, terminating a grouped expression, collection, - /// argument or parameter list: `)`. - RightParen, - /// A comma separator in a sequence: `,`. - Comma, - /// A semicolon terminating an expression: `;`. - Semicolon, - /// A colon between name/key and value in a dictionary, argument or - /// parameter list, or between the term and body of a term list term: `:`. - Colon, - /// The strong text toggle, multiplication operator, and wildcard import - /// symbol: `*`. - Star, - /// Toggles emphasized text and indicates a subscript in math: `_`. - Underscore, - /// Starts and ends a mathematical equation: `$`. - Dollar, - /// The unary plus and binary addition operator: `+`. - Plus, - /// The unary negation and binary subtraction operator: `-`. - Minus, - /// The division operator and fraction operator in math: `/`. - Slash, - /// The superscript operator in math: `^`. - Hat, - /// The field access and method call operator: `.`. - Dot, - /// The assignment operator: `=`. - Eq, - /// The equality operator: `==`. - EqEq, - /// The inequality operator: `!=`. - ExclEq, - /// The less-than operator: `<`. - Lt, - /// The less-than or equal operator: `<=`. - LtEq, - /// The greater-than operator: `>`. - Gt, - /// The greater-than or equal operator: `>=`. - GtEq, - /// The add-assign operator: `+=`. - PlusEq, - /// The subtract-assign operator: `-=`. - HyphEq, - /// The multiply-assign operator: `*=`. - StarEq, - /// The divide-assign operator: `/=`. - SlashEq, - /// The spread operator: `..`. - Dots, - /// An arrow between a closure's parameters and body: `=>`. - Arrow, - /// A root: `√`, `∛` or `∜`. - Root, - - /// The `not` operator. - Not, - /// The `and` operator. - And, - /// The `or` operator. - Or, - /// The `none` literal. - None, - /// The `auto` literal. - Auto, - /// The `let` keyword. - Let, - /// The `set` keyword. - Set, - /// The `show` keyword. - Show, - /// The `if` keyword. - If, - /// The `else` keyword. - Else, - /// The `for` keyword. - For, - /// The `in` keyword. - In, - /// The `while` keyword. - While, - /// The `break` keyword. - Break, - /// The `continue` keyword. - Continue, - /// The `return` keyword. - Return, - /// The `import` keyword. - Import, - /// The `include` keyword. - Include, - /// The `as` keyword. - As, - - /// Code. - Code, - /// An identifier: `it`. - Ident, - /// A boolean: `true`, `false`. - Bool, - /// An integer: `120`. - Int, - /// A floating-point number: `1.2`, `10e-4`. - Float, - /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`. - Numeric, - /// A quoted string: `"..."`. - Str, - /// A code block: `{ let x = 1; x + 2 }`. - CodeBlock, - /// A content block: `[*Hi* there!]`. - ContentBlock, - /// A grouped expression: `(1 + 2)`. - Parenthesized, - /// An array: `(1, "hi", 12cm)`. - Array, - /// A dictionary: `(thickness: 3pt, pattern: dashed)`. - Dict, - /// A named pair: `thickness: 3pt`. - Named, - /// A keyed pair: `"spacy key": true`. - Keyed, - /// A unary operation: `-x`. - Unary, - /// A binary operation: `a + b`. - Binary, - /// A field access: `properties.age`. - FieldAccess, - /// An invocation of a function or method: `f(x, y)`. - FuncCall, - /// A function call's argument list: `(12pt, y)`. - Args, - /// Spread arguments or an argument sink: `..x`. - Spread, - /// A closure: `(x, y) => z`. - Closure, - /// A closure's parameters: `(x, y)`. - Params, - /// A let binding: `let x = 1`. - LetBinding, - /// A set rule: `set text(...)`. - SetRule, - /// A show rule: `show heading: it => emph(it.body)`. - ShowRule, - /// An if-else conditional: `if x { y } else { z }`. - Conditional, - /// A while loop: `while x { y }`. - WhileLoop, - /// A for loop: `for x in y { z }`. - ForLoop, - /// A module import: `import "utils.typ": a, b, c`. - ModuleImport, - /// Items to import from a module: `a, b, c`. - ImportItems, - /// A module include: `include "chapter1.typ"`. - ModuleInclude, - /// A break from a loop: `break`. - LoopBreak, - /// A continue in a loop: `continue`. - LoopContinue, - /// A return from a function: `return`, `return x + 1`. - FuncReturn, - /// A destructuring pattern: `(x, _, ..y)`. - Destructuring, - /// A destructuring assignment expression: `(x, y) = (1, 2)`. - DestructAssignment, - - /// A line comment: `// ...`. - LineComment, - /// A block comment: `/* ... */`. - BlockComment, - /// An invalid sequence of characters. - Error, - /// The end of the file. - Eof, -} - -impl SyntaxKind { - /// Is this a bracket, brace, or parenthesis? - pub fn is_grouping(self) -> bool { - matches!( - self, - Self::LeftBracket - | Self::LeftBrace - | Self::LeftParen - | Self::RightBracket - | Self::RightBrace - | Self::RightParen - ) - } - - /// Does this node terminate a preceding expression? - pub fn is_terminator(self) -> bool { - matches!( - self, - Self::Eof - | Self::Semicolon - | Self::RightBrace - | Self::RightParen - | Self::RightBracket - ) - } - - /// Is this a code or content block. - pub fn is_block(self) -> bool { - matches!(self, Self::CodeBlock | Self::ContentBlock) - } - - /// Does this node need termination through a semicolon or linebreak? - pub fn is_stmt(self) -> bool { - matches!( - self, - Self::LetBinding - | Self::SetRule - | Self::ShowRule - | Self::ModuleImport - | Self::ModuleInclude - ) - } - - /// Whether this kind of node is automatically skipped by the parser in - /// code and math mode. - pub fn is_trivia(self) -> bool { - matches!( - self, - Self::Space | Self::Parbreak | Self::LineComment | Self::BlockComment - ) - } - - /// Whether this is an error. - pub fn is_error(self) -> bool { - self == Self::Error - } - - /// A human-readable name for the kind. - pub fn name(self) -> &'static str { - match self { - Self::Markup => "markup", - Self::Text => "text", - Self::Space => "space", - Self::Linebreak => "line break", - Self::Parbreak => "paragraph break", - Self::Escape => "escape sequence", - Self::Shorthand => "shorthand", - Self::SmartQuote => "smart quote", - Self::Strong => "strong content", - Self::Emph => "emphasized content", - Self::Raw => "raw block", - Self::Link => "link", - Self::Label => "label", - Self::Ref => "reference", - Self::RefMarker => "reference marker", - Self::Heading => "heading", - Self::HeadingMarker => "heading marker", - Self::ListItem => "list item", - Self::ListMarker => "list marker", - Self::EnumItem => "enum item", - Self::EnumMarker => "enum marker", - Self::TermItem => "term list item", - Self::TermMarker => "term marker", - Self::Equation => "equation", - Self::Math => "math", - Self::MathIdent => "math identifier", - Self::MathAlignPoint => "math alignment point", - Self::MathDelimited => "delimited math", - Self::MathAttach => "math attachments", - Self::MathFrac => "math fraction", - Self::MathRoot => "math root", - Self::Hashtag => "hashtag", - Self::LeftBrace => "opening brace", - Self::RightBrace => "closing brace", - Self::LeftBracket => "opening bracket", - Self::RightBracket => "closing bracket", - Self::LeftParen => "opening paren", - Self::RightParen => "closing paren", - Self::Comma => "comma", - Self::Semicolon => "semicolon", - Self::Colon => "colon", - Self::Star => "star", - Self::Underscore => "underscore", - Self::Dollar => "dollar sign", - Self::Plus => "plus", - Self::Minus => "minus", - Self::Slash => "slash", - Self::Hat => "hat", - Self::Dot => "dot", - Self::Eq => "equals sign", - Self::EqEq => "equality operator", - Self::ExclEq => "inequality operator", - Self::Lt => "less-than operator", - Self::LtEq => "less-than or equal operator", - Self::Gt => "greater-than operator", - Self::GtEq => "greater-than or equal operator", - Self::PlusEq => "add-assign operator", - Self::HyphEq => "subtract-assign operator", - Self::StarEq => "multiply-assign operator", - Self::SlashEq => "divide-assign operator", - Self::Dots => "dots", - Self::Arrow => "arrow", - Self::Root => "root", - Self::Not => "operator `not`", - Self::And => "operator `and`", - Self::Or => "operator `or`", - Self::None => "`none`", - Self::Auto => "`auto`", - Self::Let => "keyword `let`", - Self::Set => "keyword `set`", - Self::Show => "keyword `show`", - Self::If => "keyword `if`", - Self::Else => "keyword `else`", - Self::For => "keyword `for`", - Self::In => "keyword `in`", - Self::While => "keyword `while`", - Self::Break => "keyword `break`", - Self::Continue => "keyword `continue`", - Self::Return => "keyword `return`", - Self::Import => "keyword `import`", - Self::Include => "keyword `include`", - Self::As => "keyword `as`", - Self::Code => "code", - Self::Ident => "identifier", - Self::Bool => "boolean", - Self::Int => "integer", - Self::Float => "float", - Self::Numeric => "numeric value", - Self::Str => "string", - Self::CodeBlock => "code block", - Self::ContentBlock => "content block", - Self::Parenthesized => "group", - Self::Array => "array", - Self::Dict => "dictionary", - Self::Named => "named pair", - Self::Keyed => "keyed pair", - Self::Unary => "unary expression", - Self::Binary => "binary expression", - Self::FieldAccess => "field access", - Self::FuncCall => "function call", - Self::Args => "call arguments", - Self::Spread => "spread", - Self::Closure => "closure", - Self::Params => "closure parameters", - Self::LetBinding => "`let` expression", - Self::SetRule => "`set` expression", - Self::ShowRule => "`show` expression", - Self::Conditional => "`if` expression", - Self::WhileLoop => "while-loop expression", - Self::ForLoop => "for-loop expression", - Self::ModuleImport => "`import` expression", - Self::ImportItems => "import items", - Self::ModuleInclude => "`include` expression", - Self::LoopBreak => "`break` expression", - Self::LoopContinue => "`continue` expression", - Self::FuncReturn => "`return` expression", - Self::Destructuring => "destructuring pattern", - Self::DestructAssignment => "destructuring assignment expression", - Self::LineComment => "line comment", - Self::BlockComment => "block comment", - Self::Error => "syntax error", - Self::Eof => "end of file", - } - } -} diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs deleted file mode 100644 index d95b5b7b..00000000 --- a/src/syntax/lexer.rs +++ /dev/null @@ -1,738 +0,0 @@ -use ecow::{eco_format, EcoString}; -use unicode_ident::{is_xid_continue, is_xid_start}; -use unicode_segmentation::UnicodeSegmentation; -use unscanny::Scanner; - -use super::SyntaxKind; - -/// Splits up a string of source code into tokens. -#[derive(Clone)] -pub(super) struct Lexer<'s> { - /// The underlying scanner. - s: Scanner<'s>, - /// The mode the lexer is in. This determines which kinds of tokens it - /// produces. - mode: LexMode, - /// Whether the last token contained a newline. - newline: bool, - /// An error for the last token. - error: Option<EcoString>, -} - -/// What kind of tokens to emit. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub(super) enum LexMode { - /// Text and markup. - Markup, - /// Math atoms, operators, etc. - Math, - /// Keywords, literals and operators. - Code, -} - -impl<'s> Lexer<'s> { - /// Create a new lexer with the given mode and a prefix to offset column - /// calculations. - pub fn new(text: &'s str, mode: LexMode) -> Self { - Self { - s: Scanner::new(text), - mode, - newline: false, - error: None, - } - } - - /// Get the current lexing mode. - pub fn mode(&self) -> LexMode { - self.mode - } - - /// Change the lexing mode. - pub fn set_mode(&mut self, mode: LexMode) { - self.mode = mode; - } - - /// The index in the string at which the last token ends and next token - /// will start. - pub fn cursor(&self) -> usize { - self.s.cursor() - } - - /// Jump to the given index in the string. - pub fn jump(&mut self, index: usize) { - self.s.jump(index); - } - - /// Whether the last token contained a newline. - pub fn newline(&self) -> bool { - self.newline - } - - /// Take out the last error, if any. - pub fn take_error(&mut self) -> Option<EcoString> { - self.error.take() - } -} - -impl Lexer<'_> { - /// Construct a full-positioned syntax error. - fn error(&mut self, message: impl Into<EcoString>) -> SyntaxKind { - self.error = Some(message.into()); - SyntaxKind::Error - } -} - -/// Shared. -impl Lexer<'_> { - pub fn next(&mut self) -> SyntaxKind { - self.newline = false; - self.error = None; - let start = self.s.cursor(); - match self.s.eat() { - Some(c) if c.is_whitespace() => self.whitespace(start, c), - Some('/') if self.s.eat_if('/') => self.line_comment(), - Some('/') if self.s.eat_if('*') => self.block_comment(), - Some('*') if self.s.eat_if('/') => { - self.error("unexpected end of block comment") - } - - Some(c) => match self.mode { - LexMode::Markup => self.markup(start, c), - LexMode::Math => self.math(start, c), - LexMode::Code => self.code(start, c), - }, - - None => SyntaxKind::Eof, - } - } - - fn whitespace(&mut self, start: usize, c: char) -> SyntaxKind { - let more = self.s.eat_while(char::is_whitespace); - let newlines = match c { - ' ' if more.is_empty() => 0, - _ => count_newlines(self.s.from(start)), - }; - - self.newline = newlines > 0; - if self.mode == LexMode::Markup && newlines >= 2 { - SyntaxKind::Parbreak - } else { - SyntaxKind::Space - } - } - - fn line_comment(&mut self) -> SyntaxKind { - self.s.eat_until(is_newline); - SyntaxKind::LineComment - } - - fn block_comment(&mut self) -> SyntaxKind { - let mut state = '_'; - let mut depth = 1; - - // Find the first `*/` that does not correspond to a nested `/*`. - while let Some(c) = self.s.eat() { - state = match (state, c) { - ('*', '/') => { - depth -= 1; - if depth == 0 { - break; - } - '_' - } - ('/', '*') => { - depth += 1; - '_' - } - ('/', '/') => { - self.line_comment(); - '_' - } - _ => c, - } - } - - SyntaxKind::BlockComment - } -} - -/// Markup. -impl Lexer<'_> { - fn markup(&mut self, start: usize, c: char) -> SyntaxKind { - match c { - '\\' => self.backslash(), - '`' => self.raw(), - 'h' if self.s.eat_if("ttp://") => self.link(), - 'h' if self.s.eat_if("ttps://") => self.link(), - '<' if self.s.at(is_id_continue) => self.label(), - '@' => self.ref_marker(), - - '.' if self.s.eat_if("..") => SyntaxKind::Shorthand, - '-' if self.s.eat_if("--") => SyntaxKind::Shorthand, - '-' if self.s.eat_if('-') => SyntaxKind::Shorthand, - '-' if self.s.eat_if('?') => SyntaxKind::Shorthand, - '*' if !self.in_word() => SyntaxKind::Star, - '_' if !self.in_word() => SyntaxKind::Underscore, - - '#' => SyntaxKind::Hashtag, - '[' => SyntaxKind::LeftBracket, - ']' => SyntaxKind::RightBracket, - '\'' => SyntaxKind::SmartQuote, - '"' => SyntaxKind::SmartQuote, - '$' => SyntaxKind::Dollar, - '~' => SyntaxKind::Shorthand, - ':' => SyntaxKind::Colon, - '=' => { - self.s.eat_while('='); - if self.space_or_end() { - SyntaxKind::HeadingMarker - } else { - self.text() - } - } - '-' if self.space_or_end() => SyntaxKind::ListMarker, - '+' if self.space_or_end() => SyntaxKind::EnumMarker, - '/' if self.space_or_end() => SyntaxKind::TermMarker, - '0'..='9' => self.numbering(start), - - _ => self.text(), - } - } - - fn backslash(&mut self) -> SyntaxKind { - if self.s.eat_if("u{") { - let hex = self.s.eat_while(char::is_ascii_alphanumeric); - if !self.s.eat_if('}') { - return self.error("unclosed Unicode escape sequence"); - } - - if u32::from_str_radix(hex, 16) - .ok() - .and_then(std::char::from_u32) - .is_none() - { - return self.error(eco_format!("invalid Unicode codepoint: {}", hex)); - } - - return SyntaxKind::Escape; - } - - if self.s.done() || self.s.at(char::is_whitespace) { - SyntaxKind::Linebreak - } else { - self.s.eat(); - SyntaxKind::Escape - } - } - - fn raw(&mut self) -> SyntaxKind { - let mut backticks = 1; - while self.s.eat_if('`') { - backticks += 1; - } - - if backticks == 2 { - return SyntaxKind::Raw; - } - - let mut found = 0; - while found < backticks { - match self.s.eat() { - Some('`') => found += 1, - Some(_) => found = 0, - None => break, - } - } - - if found != backticks { - return self.error("unclosed raw text"); - } - - SyntaxKind::Raw - } - - fn link(&mut self) -> SyntaxKind { - let mut brackets = Vec::new(); - - #[rustfmt::skip] - self.s.eat_while(|c: char| { - match c { - | '0' ..= '9' - | 'a' ..= 'z' - | 'A' ..= 'Z' - | '!' | '#' | '$' | '%' | '&' | '*' | '+' - | ',' | '-' | '.' | '/' | ':' | ';' | '=' - | '?' | '@' | '_' | '~' | '\'' => true, - '[' => { - brackets.push(SyntaxKind::LeftBracket); - true - } - '(' => { - brackets.push(SyntaxKind::LeftParen); - true - } - ']' => brackets.pop() == Some(SyntaxKind::LeftBracket), - ')' => brackets.pop() == Some(SyntaxKind::LeftParen), - _ => false, - } - }); - - if !brackets.is_empty() { - return self.error( - "automatic links cannot contain unbalanced brackets, \ - use the `link` function instead", - ); - } - - // Don't include the trailing characters likely to be part of text. - while matches!(self.s.scout(-1), Some('!' | ',' | '.' | ':' | ';' | '?' | '\'')) { - self.s.uneat(); - } - - SyntaxKind::Link - } - - fn numbering(&mut self, start: usize) -> SyntaxKind { - self.s.eat_while(char::is_ascii_digit); - - let read = self.s.from(start); - if self.s.eat_if('.') && self.space_or_end() && read.parse::<usize>().is_ok() { - return SyntaxKind::EnumMarker; - } - - self.text() - } - - fn ref_marker(&mut self) -> SyntaxKind { - self.s.eat_while(|c| is_id_continue(c) || matches!(c, ':' | '.')); - - // Don't include the trailing characters likely to be part of text. - while matches!(self.s.scout(-1), Some('.' | ':')) { - self.s.uneat(); - } - - SyntaxKind::RefMarker - } - - fn label(&mut self) -> SyntaxKind { - let label = self.s.eat_while(|c| is_id_continue(c) || matches!(c, ':' | '.')); - if label.is_empty() { - return self.error("label cannot be empty"); - } - - if !self.s.eat_if('>') { - return self.error("unclosed label"); - } - - SyntaxKind::Label - } - - fn text(&mut self) -> SyntaxKind { - macro_rules! table { - ($(|$c:literal)*) => { - static TABLE: [bool; 128] = { - let mut t = [false; 128]; - $(t[$c as usize] = true;)* - t - }; - }; - } - - table! { - | ' ' | '\t' | '\n' | '\x0b' | '\x0c' | '\r' | '\\' | '/' - | '[' | ']' | '{' | '}' | '~' | '-' | '.' | '\'' | '"' - | '*' | '_' | ':' | 'h' | '`' | '$' | '<' | '>' | '@' | '#' - }; - - loop { - self.s.eat_until(|c: char| { - TABLE.get(c as usize).copied().unwrap_or_else(|| c.is_whitespace()) - }); - - // Continue with the same text node if the thing would become text - // anyway. - let mut s = self.s; - match s.eat() { - Some(' ') if s.at(char::is_alphanumeric) => {} - Some('/') if !s.at(['/', '*']) => {} - Some('-') if !s.at(['-', '?']) => {} - Some('.') if !s.at("..") => {} - Some('h') if !s.at("ttp://") && !s.at("ttps://") => {} - Some('@') if !s.at(is_id_start) => {} - _ => break, - } - - self.s = s; - } - - SyntaxKind::Text - } - - fn in_word(&self) -> bool { - let alphanum = |c: Option<char>| c.map_or(false, |c| c.is_alphanumeric()); - let prev = self.s.scout(-2); - let next = self.s.peek(); - alphanum(prev) && alphanum(next) - } - - fn space_or_end(&self) -> bool { - self.s.done() || self.s.at(char::is_whitespace) - } -} - -/// Math. -impl Lexer<'_> { - fn math(&mut self, start: usize, c: char) -> SyntaxKind { - match c { - '\\' => self.backslash(), - '"' => self.string(), - - '-' if self.s.eat_if(">>") => SyntaxKind::Shorthand, - '-' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '-' if self.s.eat_if("->") => SyntaxKind::Shorthand, - ':' if self.s.eat_if('=') => SyntaxKind::Shorthand, - ':' if self.s.eat_if(":=") => SyntaxKind::Shorthand, - '!' if self.s.eat_if('=') => SyntaxKind::Shorthand, - '.' if self.s.eat_if("..") => SyntaxKind::Shorthand, - '[' if self.s.eat_if('|') => SyntaxKind::Shorthand, - '<' if self.s.eat_if("==>") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("-->") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("--") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("-<") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("->") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("<-") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("<<") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("==") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("~~") => SyntaxKind::Shorthand, - '<' if self.s.eat_if('=') => SyntaxKind::Shorthand, - '<' if self.s.eat_if('<') => SyntaxKind::Shorthand, - '<' if self.s.eat_if('-') => SyntaxKind::Shorthand, - '<' if self.s.eat_if('~') => SyntaxKind::Shorthand, - '>' if self.s.eat_if("->") => SyntaxKind::Shorthand, - '>' if self.s.eat_if(">>") => SyntaxKind::Shorthand, - '=' if self.s.eat_if("=>") => SyntaxKind::Shorthand, - '=' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '=' if self.s.eat_if(':') => SyntaxKind::Shorthand, - '>' if self.s.eat_if('=') => SyntaxKind::Shorthand, - '>' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '|' if self.s.eat_if("->") => SyntaxKind::Shorthand, - '|' if self.s.eat_if("=>") => SyntaxKind::Shorthand, - '|' if self.s.eat_if(']') => SyntaxKind::Shorthand, - '|' if self.s.eat_if('|') => SyntaxKind::Shorthand, - '~' if self.s.eat_if("~>") => SyntaxKind::Shorthand, - '~' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '*' | '\'' | '-' => SyntaxKind::Shorthand, - - '#' => SyntaxKind::Hashtag, - '_' => SyntaxKind::Underscore, - '$' => SyntaxKind::Dollar, - '/' => SyntaxKind::Slash, - '^' => SyntaxKind::Hat, - '&' => SyntaxKind::MathAlignPoint, - '√' | '∛' | '∜' => SyntaxKind::Root, - - // Identifiers. - c if is_math_id_start(c) && self.s.at(is_math_id_continue) => { - self.s.eat_while(is_math_id_continue); - SyntaxKind::MathIdent - } - - // Other math atoms. - _ => self.math_text(start, c), - } - } - - fn math_text(&mut self, start: usize, c: char) -> SyntaxKind { - // Keep numbers and grapheme clusters together. - if c.is_numeric() { - self.s.eat_while(char::is_numeric); - let mut s = self.s; - if s.eat_if('.') && !s.eat_while(char::is_numeric).is_empty() { - self.s = s; - } - } else { - let len = self - .s - .get(start..self.s.string().len()) - .graphemes(true) - .next() - .map_or(0, str::len); - self.s.jump(start + len); - } - SyntaxKind::Text - } -} - -/// Code. -impl Lexer<'_> { - fn code(&mut self, start: usize, c: char) -> SyntaxKind { - match c { - '`' => self.raw(), - '<' if self.s.at(is_id_continue) => self.label(), - '0'..='9' => self.number(start, c), - '.' if self.s.at(char::is_ascii_digit) => self.number(start, c), - '"' => self.string(), - - '=' if self.s.eat_if('=') => SyntaxKind::EqEq, - '!' if self.s.eat_if('=') => SyntaxKind::ExclEq, - '<' if self.s.eat_if('=') => SyntaxKind::LtEq, - '>' if self.s.eat_if('=') => SyntaxKind::GtEq, - '+' if self.s.eat_if('=') => SyntaxKind::PlusEq, - '-' if self.s.eat_if('=') => SyntaxKind::HyphEq, - '*' if self.s.eat_if('=') => SyntaxKind::StarEq, - '/' if self.s.eat_if('=') => SyntaxKind::SlashEq, - '.' if self.s.eat_if('.') => SyntaxKind::Dots, - '=' if self.s.eat_if('>') => SyntaxKind::Arrow, - - '{' => SyntaxKind::LeftBrace, - '}' => SyntaxKind::RightBrace, - '[' => SyntaxKind::LeftBracket, - ']' => SyntaxKind::RightBracket, - '(' => SyntaxKind::LeftParen, - ')' => SyntaxKind::RightParen, - '$' => SyntaxKind::Dollar, - ',' => SyntaxKind::Comma, - ';' => SyntaxKind::Semicolon, - ':' => SyntaxKind::Colon, - '.' => SyntaxKind::Dot, - '+' => SyntaxKind::Plus, - '-' => SyntaxKind::Minus, - '*' => SyntaxKind::Star, - '/' => SyntaxKind::Slash, - '=' => SyntaxKind::Eq, - '<' => SyntaxKind::Lt, - '>' => SyntaxKind::Gt, - - c if is_id_start(c) => self.ident(start), - - c => self.error(eco_format!("the character `{c}` is not valid in code")), - } - } - - fn ident(&mut self, start: usize) -> SyntaxKind { - self.s.eat_while(is_id_continue); - let ident = self.s.from(start); - - let prev = self.s.get(0..start); - if !prev.ends_with(['.', '@']) || prev.ends_with("..") { - if let Some(keyword) = keyword(ident) { - return keyword; - } - } - - if ident == "_" { - SyntaxKind::Underscore - } else { - SyntaxKind::Ident - } - } - - fn number(&mut self, mut start: usize, c: char) -> SyntaxKind { - // Handle alternative integer bases. - let mut base = 10; - if c == '0' { - if self.s.eat_if('b') { - base = 2; - } else if self.s.eat_if('o') { - base = 8; - } else if self.s.eat_if('x') { - base = 16; - } - if base != 10 { - start = self.s.cursor(); - } - } - - // Read the first part (integer or fractional depending on `first`). - self.s.eat_while(if base == 16 { - char::is_ascii_alphanumeric - } else { - char::is_ascii_digit - }); - - // Read the fractional part if not already done. - // Make sure not to confuse a range for the decimal separator. - if c != '.' - && !self.s.at("..") - && !self.s.scout(1).map_or(false, is_id_start) - && self.s.eat_if('.') - && base == 10 - { - self.s.eat_while(char::is_ascii_digit); - } - - // Read the exponent. - if !self.s.at("em") && self.s.eat_if(['e', 'E']) && base == 10 { - self.s.eat_if(['+', '-']); - self.s.eat_while(char::is_ascii_digit); - } - - // Read the suffix. - let suffix_start = self.s.cursor(); - if !self.s.eat_if('%') { - self.s.eat_while(char::is_ascii_alphanumeric); - } - - let number = self.s.get(start..suffix_start); - let suffix = self.s.from(suffix_start); - - let kind = if i64::from_str_radix(number, base).is_ok() { - SyntaxKind::Int - } else if base == 10 && number.parse::<f64>().is_ok() { - SyntaxKind::Float - } else { - return self.error(match base { - 2 => eco_format!("invalid binary number: 0b{}", number), - 8 => eco_format!("invalid octal number: 0o{}", number), - 16 => eco_format!("invalid hexadecimal number: 0x{}", number), - _ => eco_format!("invalid number: {}", number), - }); - }; - - if suffix.is_empty() { - return kind; - } - - if !matches!( - suffix, - "pt" | "mm" | "cm" | "in" | "deg" | "rad" | "em" | "fr" | "%" - ) { - return self.error(eco_format!("invalid number suffix: {}", suffix)); - } - - SyntaxKind::Numeric - } - - fn string(&mut self) -> SyntaxKind { - let mut escaped = false; - self.s.eat_until(|c| { - let stop = c == '"' && !escaped; - escaped = c == '\\' && !escaped; - stop - }); - - if !self.s.eat_if('"') { - return self.error("unclosed string"); - } - - SyntaxKind::Str - } -} - -/// Try to parse an identifier into a keyword. -fn keyword(ident: &str) -> Option<SyntaxKind> { - Some(match ident { - "none" => SyntaxKind::None, - "auto" => SyntaxKind::Auto, - "true" => SyntaxKind::Bool, - "false" => SyntaxKind::Bool, - "not" => SyntaxKind::Not, - "and" => SyntaxKind::And, - "or" => SyntaxKind::Or, - "let" => SyntaxKind::Let, - "set" => SyntaxKind::Set, - "show" => SyntaxKind::Show, - "if" => SyntaxKind::If, - "else" => SyntaxKind::Else, - "for" => SyntaxKind::For, - "in" => SyntaxKind::In, - "while" => SyntaxKind::While, - "break" => SyntaxKind::Break, - "continue" => SyntaxKind::Continue, - "return" => SyntaxKind::Return, - "import" => SyntaxKind::Import, - "include" => SyntaxKind::Include, - "as" => SyntaxKind::As, - _ => return None, - }) -} - -/// Whether this character denotes a newline. -#[inline] -pub fn is_newline(character: char) -> bool { - matches!( - character, - // Line Feed, Vertical Tab, Form Feed, Carriage Return. - '\n' | '\x0B' | '\x0C' | '\r' | - // Next Line, Line Separator, Paragraph Separator. - '\u{0085}' | '\u{2028}' | '\u{2029}' - ) -} - -/// Split text at newlines. -pub(super) fn split_newlines(text: &str) -> Vec<&str> { - let mut s = Scanner::new(text); - let mut lines = Vec::new(); - let mut start = 0; - let mut end = 0; - - while let Some(c) = s.eat() { - if is_newline(c) { - if c == '\r' { - s.eat_if('\n'); - } - - lines.push(&text[start..end]); - start = s.cursor(); - } - end = s.cursor(); - } - - lines.push(&text[start..]); - lines -} - -/// Count the number of newlines in text. -fn count_newlines(text: &str) -> usize { - let mut newlines = 0; - let mut s = Scanner::new(text); - while let Some(c) = s.eat() { - if is_newline(c) { - if c == '\r' { - s.eat_if('\n'); - } - newlines += 1; - } - } - newlines -} - -/// Whether a string is a valid Typst identifier. -/// -/// In addition to what is specified in the [Unicode Standard][uax31], we allow: -/// - `_` as a starting character, -/// - `_` and `-` as continuing characters. -/// -/// [uax31]: http://www.unicode.org/reports/tr31/ -#[inline] -pub fn is_ident(string: &str) -> bool { - let mut chars = string.chars(); - chars - .next() - .map_or(false, |c| is_id_start(c) && chars.all(is_id_continue)) -} - -/// Whether a character can start an identifier. -#[inline] -pub(crate) fn is_id_start(c: char) -> bool { - is_xid_start(c) || c == '_' -} - -/// Whether a character can continue an identifier. -#[inline] -pub(crate) fn is_id_continue(c: char) -> bool { - is_xid_continue(c) || c == '_' || c == '-' -} - -/// Whether a character can start an identifier in math. -#[inline] -fn is_math_id_start(c: char) -> bool { - is_xid_start(c) -} - -/// Whether a character can continue an identifier in math. -#[inline] -fn is_math_id_continue(c: char) -> bool { - is_xid_continue(c) && c != '_' -} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs deleted file mode 100644 index 1ce1e4c0..00000000 --- a/src/syntax/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Syntax definition, parsing, and highlighting. - -pub mod ast; - -mod kind; -mod lexer; -mod node; -mod parser; -mod reparser; -mod source; -mod span; - -pub use self::kind::SyntaxKind; -pub use self::lexer::{is_ident, is_newline}; -pub use self::node::{LinkedChildren, LinkedNode, SyntaxNode}; -pub use self::parser::{parse, parse_code}; -pub use self::source::Source; -pub use self::span::{Span, Spanned}; - -pub(crate) use self::lexer::{is_id_continue, is_id_start}; - -use self::lexer::{split_newlines, LexMode, Lexer}; -use self::parser::{reparse_block, reparse_markup}; diff --git a/src/syntax/node.rs b/src/syntax/node.rs deleted file mode 100644 index 6a66416d..00000000 --- a/src/syntax/node.rs +++ /dev/null @@ -1,889 +0,0 @@ -use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::{Deref, Range}; -use std::rc::Rc; -use std::sync::Arc; - -use ecow::EcoString; - -use super::ast::AstNode; -use super::{Span, SyntaxKind}; -use crate::diag::SourceError; -use crate::file::FileId; - -/// A node in the untyped syntax tree. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct SyntaxNode(Repr); - -/// The three internal representations. -#[derive(Clone, Eq, PartialEq, Hash)] -enum Repr { - /// A leaf node. - Leaf(LeafNode), - /// A reference-counted inner node. - Inner(Arc<InnerNode>), - /// An error node. - Error(Arc<ErrorNode>), -} - -impl SyntaxNode { - /// Create a new leaf node. - pub fn leaf(kind: SyntaxKind, text: impl Into<EcoString>) -> Self { - Self(Repr::Leaf(LeafNode::new(kind, text))) - } - - /// Create a new inner node with children. - pub fn inner(kind: SyntaxKind, children: Vec<SyntaxNode>) -> Self { - Self(Repr::Inner(Arc::new(InnerNode::new(kind, children)))) - } - - /// Create a new error node. - pub fn error(message: impl Into<EcoString>, text: impl Into<EcoString>) -> Self { - Self(Repr::Error(Arc::new(ErrorNode::new(message, text)))) - } - - /// The type of the node. - pub fn kind(&self) -> SyntaxKind { - match &self.0 { - Repr::Leaf(leaf) => leaf.kind, - Repr::Inner(inner) => inner.kind, - Repr::Error(_) => SyntaxKind::Error, - } - } - - /// Return `true` if the length is 0. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// The byte length of the node in the source text. - pub fn len(&self) -> usize { - match &self.0 { - Repr::Leaf(leaf) => leaf.len(), - Repr::Inner(inner) => inner.len, - Repr::Error(error) => error.len(), - } - } - - /// The span of the node. - pub fn span(&self) -> Span { - match &self.0 { - Repr::Leaf(leaf) => leaf.span, - Repr::Inner(inner) => inner.span, - Repr::Error(error) => error.span, - } - } - - /// The text of the node if it is a leaf node. - /// - /// Returns the empty string if this is an inner node. - pub fn text(&self) -> &EcoString { - static EMPTY: EcoString = EcoString::new(); - match &self.0 { - Repr::Leaf(leaf) => &leaf.text, - Repr::Error(error) => &error.text, - Repr::Inner(_) => &EMPTY, - } - } - - /// Extract the text from the node. - /// - /// Builds the string if this is an inner node. - pub fn into_text(self) -> EcoString { - match self.0 { - Repr::Leaf(leaf) => leaf.text, - Repr::Error(error) => error.text.clone(), - Repr::Inner(node) => { - node.children.iter().cloned().map(Self::into_text).collect() - } - } - } - - /// The node's children. - pub fn children(&self) -> std::slice::Iter<'_, SyntaxNode> { - match &self.0 { - Repr::Leaf(_) | Repr::Error(_) => [].iter(), - Repr::Inner(inner) => inner.children.iter(), - } - } - - /// Whether the node can be cast to the given AST node. - pub fn is<T: AstNode>(&self) -> bool { - self.cast::<T>().is_some() - } - - /// Try to convert the node to a typed AST node. - pub fn cast<T: AstNode>(&self) -> Option<T> { - T::from_untyped(self) - } - - /// Cast the first child that can cast to the AST type `T`. - pub fn cast_first_match<T: AstNode>(&self) -> Option<T> { - self.children().find_map(Self::cast) - } - - /// Cast the last child that can cast to the AST type `T`. - pub fn cast_last_match<T: AstNode>(&self) -> Option<T> { - self.children().rev().find_map(Self::cast) - } - - /// Whether the node or its children contain an error. - pub fn erroneous(&self) -> bool { - match &self.0 { - Repr::Leaf(_) => false, - Repr::Inner(node) => node.erroneous, - Repr::Error(_) => true, - } - } - - /// The error messages for this node and its descendants. - pub fn errors(&self) -> Vec<SourceError> { - if !self.erroneous() { - return vec![]; - } - - if let Repr::Error(error) = &self.0 { - vec![SourceError::new(error.span, error.message.clone())] - } else { - self.children() - .filter(|node| node.erroneous()) - .flat_map(|node| node.errors()) - .collect() - } - } - - /// Set a synthetic span for the node and all its descendants. - pub fn synthesize(&mut self, span: Span) { - match &mut self.0 { - Repr::Leaf(leaf) => leaf.span = span, - Repr::Inner(inner) => Arc::make_mut(inner).synthesize(span), - Repr::Error(error) => Arc::make_mut(error).span = span, - } - } -} - -impl SyntaxNode { - /// Mark this node as erroneous. - pub(super) fn make_erroneous(&mut self) { - if let Repr::Inner(inner) = &mut self.0 { - Arc::make_mut(inner).erroneous = true; - } - } - - /// Convert the child to another kind. - #[track_caller] - pub(super) fn convert_to_kind(&mut self, kind: SyntaxKind) { - debug_assert!(!kind.is_error()); - match &mut self.0 { - Repr::Leaf(leaf) => leaf.kind = kind, - Repr::Inner(inner) => Arc::make_mut(inner).kind = kind, - Repr::Error(_) => panic!("cannot convert error"), - } - } - - /// Convert the child to an error. - pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) { - let text = std::mem::take(self).into_text(); - *self = SyntaxNode::error(message, text); - } - - /// Assign spans to each node. - #[tracing::instrument(skip_all)] - pub(super) fn numberize( - &mut self, - id: FileId, - within: Range<u64>, - ) -> NumberingResult { - if within.start >= within.end { - return Err(Unnumberable); - } - - let mid = Span::new(id, (within.start + within.end) / 2); - match &mut self.0 { - Repr::Leaf(leaf) => leaf.span = mid, - Repr::Inner(inner) => Arc::make_mut(inner).numberize(id, None, within)?, - Repr::Error(error) => Arc::make_mut(error).span = mid, - } - - Ok(()) - } - - /// Whether this is a leaf node. - pub(super) fn is_leaf(&self) -> bool { - matches!(self.0, Repr::Leaf(_)) - } - - /// The number of descendants, including the node itself. - pub(super) fn descendants(&self) -> usize { - match &self.0 { - Repr::Leaf(_) | Repr::Error(_) => 1, - Repr::Inner(inner) => inner.descendants, - } - } - - /// The node's children, mutably. - pub(super) fn children_mut(&mut self) -> &mut [SyntaxNode] { - match &mut self.0 { - Repr::Leaf(_) | Repr::Error(_) => &mut [], - Repr::Inner(inner) => &mut Arc::make_mut(inner).children, - } - } - - /// Replaces a range of children with a replacement. - /// - /// May have mutated the children if it returns `Err(_)`. - pub(super) fn replace_children( - &mut self, - range: Range<usize>, - replacement: Vec<SyntaxNode>, - ) -> NumberingResult { - if let Repr::Inner(inner) = &mut self.0 { - Arc::make_mut(inner).replace_children(range, replacement)?; - } - Ok(()) - } - - /// Update this node after changes were made to one of its children. - pub(super) fn update_parent( - &mut self, - prev_len: usize, - new_len: usize, - prev_descendants: usize, - new_descendants: usize, - ) { - if let Repr::Inner(inner) = &mut self.0 { - Arc::make_mut(inner).update_parent( - prev_len, - new_len, - prev_descendants, - new_descendants, - ); - } - } - - /// The upper bound of assigned numbers in this subtree. - pub(super) fn upper(&self) -> u64 { - match &self.0 { - Repr::Inner(inner) => inner.upper, - Repr::Leaf(leaf) => leaf.span.number() + 1, - Repr::Error(error) => error.span.number() + 1, - } - } -} - -impl Debug for SyntaxNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match &self.0 { - Repr::Inner(node) => node.fmt(f), - Repr::Leaf(node) => node.fmt(f), - Repr::Error(node) => node.fmt(f), - } - } -} - -impl Default for SyntaxNode { - fn default() -> Self { - Self::error("", "") - } -} - -/// A leaf node in the untyped syntax tree. -#[derive(Clone, Eq, PartialEq, Hash)] -struct LeafNode { - /// What kind of node this is (each kind would have its own struct in a - /// strongly typed AST). - kind: SyntaxKind, - /// The source text of the node. - text: EcoString, - /// The node's span. - span: Span, -} - -impl LeafNode { - /// Create a new leaf node. - #[track_caller] - fn new(kind: SyntaxKind, text: impl Into<EcoString>) -> Self { - debug_assert!(!kind.is_error()); - Self { kind, text: text.into(), span: Span::detached() } - } - - /// The byte length of the node in the source text. - fn len(&self) -> usize { - self.text.len() - } -} - -impl Debug for LeafNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{:?}: {:?}", self.kind, self.text) - } -} - -/// An inner node in the untyped syntax tree. -#[derive(Clone, Eq, PartialEq, Hash)] -struct InnerNode { - /// What kind of node this is (each kind would have its own struct in a - /// strongly typed AST). - kind: SyntaxKind, - /// The byte length of the node in the source. - len: usize, - /// The node's span. - span: Span, - /// The number of nodes in the whole subtree, including this node. - descendants: usize, - /// Whether this node or any of its children are erroneous. - erroneous: bool, - /// The upper bound of this node's numbering range. - upper: u64, - /// This node's children, losslessly make up this node. - children: Vec<SyntaxNode>, -} - -impl InnerNode { - /// Create a new inner node with the given kind and children. - #[track_caller] - fn new(kind: SyntaxKind, children: Vec<SyntaxNode>) -> Self { - debug_assert!(!kind.is_error()); - - let mut len = 0; - let mut descendants = 1; - let mut erroneous = false; - - for child in &children { - len += child.len(); - descendants += child.descendants(); - erroneous |= child.erroneous(); - } - - Self { - kind, - len, - span: Span::detached(), - descendants, - erroneous, - upper: 0, - children, - } - } - - /// Set a synthetic span for the node and all its descendants. - fn synthesize(&mut self, span: Span) { - self.span = span; - self.upper = span.number(); - for child in &mut self.children { - child.synthesize(span); - } - } - - /// Assign span numbers `within` an interval to this node's subtree or just - /// a `range` of its children. - fn numberize( - &mut self, - id: FileId, - range: Option<Range<usize>>, - within: Range<u64>, - ) -> NumberingResult { - // Determine how many nodes we will number. - let descendants = match &range { - Some(range) if range.is_empty() => return Ok(()), - Some(range) => self.children[range.clone()] - .iter() - .map(SyntaxNode::descendants) - .sum::<usize>(), - None => self.descendants, - }; - - // Determine the distance between two neighbouring assigned numbers. If - // possible, we try to fit all numbers into the left half of `within` - // so that there is space for future insertions. - let space = within.end - within.start; - let mut stride = space / (2 * descendants as u64); - if stride == 0 { - stride = space / self.descendants as u64; - if stride == 0 { - return Err(Unnumberable); - } - } - - // Number the node itself. - let mut start = within.start; - if range.is_none() { - let end = start + stride; - self.span = Span::new(id, (start + end) / 2); - self.upper = within.end; - start = end; - } - - // Number the children. - let len = self.children.len(); - for child in &mut self.children[range.unwrap_or(0..len)] { - let end = start + child.descendants() as u64 * stride; - child.numberize(id, start..end)?; - start = end; - } - - Ok(()) - } - - /// Replaces a range of children with a replacement. - /// - /// May have mutated the children if it returns `Err(_)`. - fn replace_children( - &mut self, - mut range: Range<usize>, - replacement: Vec<SyntaxNode>, - ) -> NumberingResult { - let superseded = &self.children[range.clone()]; - - // Compute the new byte length. - self.len = self.len + replacement.iter().map(SyntaxNode::len).sum::<usize>() - - superseded.iter().map(SyntaxNode::len).sum::<usize>(); - - // Compute the new number of descendants. - self.descendants = self.descendants - + replacement.iter().map(SyntaxNode::descendants).sum::<usize>() - - superseded.iter().map(SyntaxNode::descendants).sum::<usize>(); - - // Determine whether we're still erroneous after the replacement. That's - // the case if - // - any of the new nodes is erroneous, - // - or if we were erroneous before due to a non-superseded node. - self.erroneous = replacement.iter().any(SyntaxNode::erroneous) - || (self.erroneous - && (self.children[..range.start].iter().any(SyntaxNode::erroneous)) - || self.children[range.end..].iter().any(SyntaxNode::erroneous)); - - // Perform the replacement. - let replacement_count = replacement.len(); - self.children.splice(range.clone(), replacement); - range.end = range.start + replacement_count; - - // Renumber the new children. Retries until it works, taking - // exponentially more children into account. - let mut left = 0; - let mut right = 0; - let max_left = range.start; - let max_right = self.children.len() - range.end; - loop { - let renumber = range.start - left..range.end + right; - - // The minimum assignable number is either - // - the upper bound of the node right before the to-be-renumbered - // children, - // - or this inner node's span number plus one if renumbering starts - // at the first child. - let start_number = renumber - .start - .checked_sub(1) - .and_then(|i| self.children.get(i)) - .map_or(self.span.number() + 1, |child| child.upper()); - - // The upper bound for renumbering is either - // - the span number of the first child after the to-be-renumbered - // children, - // - or this node's upper bound if renumbering ends behind the last - // child. - let end_number = self - .children - .get(renumber.end) - .map_or(self.upper, |next| next.span().number()); - - // Try to renumber. - let within = start_number..end_number; - let id = self.span.id(); - if self.numberize(id, Some(renumber), within).is_ok() { - return Ok(()); - } - - // If it didn't even work with all children, we give up. - if left == max_left && right == max_right { - return Err(Unnumberable); - } - - // Exponential expansion to both sides. - left = (left + 1).next_power_of_two().min(max_left); - right = (right + 1).next_power_of_two().min(max_right); - } - } - - /// Update this node after changes were made to one of its children. - fn update_parent( - &mut self, - prev_len: usize, - new_len: usize, - prev_descendants: usize, - new_descendants: usize, - ) { - self.len = self.len + new_len - prev_len; - self.descendants = self.descendants + new_descendants - prev_descendants; - self.erroneous = self.children.iter().any(SyntaxNode::erroneous); - } -} - -impl Debug for InnerNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{:?}: {}", self.kind, self.len)?; - if !self.children.is_empty() { - f.write_str(" ")?; - f.debug_list().entries(&self.children).finish()?; - } - Ok(()) - } -} - -/// An error node in the untyped syntax tree. -#[derive(Clone, Eq, PartialEq, Hash)] -struct ErrorNode { - /// The error message. - message: EcoString, - /// The source text of the node. - text: EcoString, - /// The node's span. - span: Span, -} - -impl ErrorNode { - /// Create new error node. - fn new(message: impl Into<EcoString>, text: impl Into<EcoString>) -> Self { - Self { - message: message.into(), - text: text.into(), - span: Span::detached(), - } - } - - /// The byte length of the node in the source text. - fn len(&self) -> usize { - self.text.len() - } -} - -impl Debug for ErrorNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Error: {:?} ({})", self.text, self.message) - } -} - -/// A syntax node in a context. -/// -/// Knows its exact offset in the file and provides access to its -/// children, parent and siblings. -/// -/// **Note that all sibling and leaf accessors skip over trivia!** -#[derive(Clone)] -pub struct LinkedNode<'a> { - node: &'a SyntaxNode, - parent: Option<Rc<Self>>, - index: usize, - offset: usize, -} - -impl<'a> LinkedNode<'a> { - /// Start a new traversal at a root node. - pub fn new(root: &'a SyntaxNode) -> Self { - Self { node: root, parent: None, index: 0, offset: 0 } - } - - /// Get the contained syntax node. - pub fn get(&self) -> &'a SyntaxNode { - self.node - } - - /// The index of this node in its parent's children list. - pub fn index(&self) -> usize { - self.index - } - - /// The absolute byte offset of this node in the source file. - pub fn offset(&self) -> usize { - self.offset - } - - /// The byte range of this node in the source file. - pub fn range(&self) -> Range<usize> { - self.offset..self.offset + self.node.len() - } - - /// An iterator over this node's children. - pub fn children(&self) -> LinkedChildren<'a> { - LinkedChildren { - parent: Rc::new(self.clone()), - iter: self.node.children().enumerate(), - front: self.offset, - back: self.offset + self.len(), - } - } - - /// Find a descendant with the given span. - pub fn find(&self, span: Span) -> Option<LinkedNode<'a>> { - if self.span() == span { - return Some(self.clone()); - } - - if let Repr::Inner(inner) = &self.0 { - // The parent of a subtree has a smaller span number than all of its - // descendants. Therefore, we can bail out early if the target span's - // number is smaller than our number. - if span.number() < inner.span.number() { - return None; - } - - let mut children = self.children().peekable(); - while let Some(child) = children.next() { - // Every node in this child's subtree has a smaller span number than - // the next sibling. Therefore we only need to recurse if the next - // sibling's span number is larger than the target span's number. - if children - .peek() - .map_or(true, |next| next.span().number() > span.number()) - { - if let Some(found) = child.find(span) { - return Some(found); - } - } - } - } - - None - } -} - -/// Access to parents and siblings. -impl<'a> LinkedNode<'a> { - /// Get this node's parent. - pub fn parent(&self) -> Option<&Self> { - self.parent.as_deref() - } - - /// Get the first previous non-trivia sibling node. - pub fn prev_sibling(&self) -> Option<Self> { - let parent = self.parent()?; - let index = self.index.checked_sub(1)?; - let node = parent.node.children().nth(index)?; - let offset = self.offset - node.len(); - let prev = Self { node, parent: self.parent.clone(), index, offset }; - if prev.kind().is_trivia() { - prev.prev_sibling() - } else { - Some(prev) - } - } - - /// Get the next non-trivia sibling node. - pub fn next_sibling(&self) -> Option<Self> { - let parent = self.parent()?; - let index = self.index.checked_add(1)?; - let node = parent.node.children().nth(index)?; - let offset = self.offset + self.node.len(); - let next = Self { node, parent: self.parent.clone(), index, offset }; - if next.kind().is_trivia() { - next.next_sibling() - } else { - Some(next) - } - } - - /// Get the kind of this node's parent. - pub fn parent_kind(&self) -> Option<SyntaxKind> { - Some(self.parent()?.node.kind()) - } - - /// Get the kind of this node's first previous non-trivia sibling. - pub fn prev_sibling_kind(&self) -> Option<SyntaxKind> { - Some(self.prev_sibling()?.node.kind()) - } - - /// Get the kind of this node's next non-trivia sibling. - pub fn next_sibling_kind(&self) -> Option<SyntaxKind> { - Some(self.next_sibling()?.node.kind()) - } -} - -/// Access to leafs. -impl<'a> LinkedNode<'a> { - /// Get the rightmost non-trivia leaf before this node. - pub fn prev_leaf(&self) -> Option<Self> { - let mut node = self.clone(); - while let Some(prev) = node.prev_sibling() { - if let Some(leaf) = prev.rightmost_leaf() { - return Some(leaf); - } - node = prev; - } - self.parent()?.prev_leaf() - } - - /// Find the leftmost contained non-trivia leaf. - pub fn leftmost_leaf(&self) -> Option<Self> { - if self.is_leaf() && !self.kind().is_trivia() && !self.kind().is_error() { - return Some(self.clone()); - } - - for child in self.children() { - if let Some(leaf) = child.leftmost_leaf() { - return Some(leaf); - } - } - - None - } - - /// Get the leaf at the specified byte offset. - pub fn leaf_at(&self, cursor: usize) -> Option<Self> { - if self.node.children().len() == 0 && cursor <= self.offset + self.len() { - return Some(self.clone()); - } - - let mut offset = self.offset; - let count = self.node.children().len(); - for (i, child) in self.children().enumerate() { - let len = child.len(); - if (offset < cursor && cursor <= offset + len) - || (offset == cursor && i + 1 == count) - { - return child.leaf_at(cursor); - } - offset += len; - } - - None - } - - /// Find the rightmost contained non-trivia leaf. - pub fn rightmost_leaf(&self) -> Option<Self> { - if self.is_leaf() && !self.kind().is_trivia() { - return Some(self.clone()); - } - - for child in self.children().rev() { - if let Some(leaf) = child.rightmost_leaf() { - return Some(leaf); - } - } - - None - } - - /// Get the leftmost non-trivia leaf after this node. - pub fn next_leaf(&self) -> Option<Self> { - let mut node = self.clone(); - while let Some(next) = node.next_sibling() { - if let Some(leaf) = next.leftmost_leaf() { - return Some(leaf); - } - node = next; - } - self.parent()?.next_leaf() - } -} - -impl Deref for LinkedNode<'_> { - type Target = SyntaxNode; - - fn deref(&self) -> &Self::Target { - self.get() - } -} - -impl Debug for LinkedNode<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.node.fmt(f) - } -} - -/// An iterator over the children of a linked node. -pub struct LinkedChildren<'a> { - parent: Rc<LinkedNode<'a>>, - iter: std::iter::Enumerate<std::slice::Iter<'a, SyntaxNode>>, - front: usize, - back: usize, -} - -impl<'a> Iterator for LinkedChildren<'a> { - type Item = LinkedNode<'a>; - - fn next(&mut self) -> Option<Self::Item> { - self.iter.next().map(|(index, node)| { - let offset = self.front; - self.front += node.len(); - LinkedNode { - node, - parent: Some(self.parent.clone()), - index, - offset, - } - }) - } - - fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() - } -} - -impl DoubleEndedIterator for LinkedChildren<'_> { - fn next_back(&mut self) -> Option<Self::Item> { - self.iter.next_back().map(|(index, node)| { - self.back -= node.len(); - LinkedNode { - node, - parent: Some(self.parent.clone()), - index, - offset: self.back, - } - }) - } -} - -impl ExactSizeIterator for LinkedChildren<'_> {} - -/// Result of numbering a node within an interval. -pub(super) type NumberingResult = Result<(), Unnumberable>; - -/// Indicates that a node cannot be numbered within a given interval. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub(super) struct Unnumberable; - -impl Display for Unnumberable { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("cannot number within this interval") - } -} - -impl std::error::Error for Unnumberable {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::Source; - - #[test] - fn test_linked_node() { - let source = Source::detached("#set text(12pt, red)"); - - // Find "text". - let node = LinkedNode::new(source.root()).leaf_at(7).unwrap(); - assert_eq!(node.offset(), 5); - assert_eq!(node.text(), "text"); - - // Go back to "#set". Skips the space. - let prev = node.prev_sibling().unwrap(); - assert_eq!(prev.offset(), 1); - assert_eq!(prev.text(), "set"); - } - - #[test] - fn test_linked_node_non_trivia_leaf() { - let source = Source::detached("#set fun(12pt, red)"); - let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap(); - let prev = leaf.prev_leaf().unwrap(); - assert_eq!(leaf.text(), "fun"); - assert_eq!(prev.text(), "set"); - - let source = Source::detached("#let x = 10"); - let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap(); - let prev = leaf.prev_leaf().unwrap(); - let next = leaf.next_leaf().unwrap(); - assert_eq!(prev.text(), "="); - assert_eq!(leaf.text(), " "); - assert_eq!(next.text(), "10"); - } -} diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs deleted file mode 100644 index 54670df5..00000000 --- a/src/syntax/parser.rs +++ /dev/null @@ -1,1643 +0,0 @@ -use std::collections::HashSet; -use std::ops::Range; - -use ecow::{eco_format, EcoString}; -use unicode_math_class::MathClass; - -use super::{ast, is_newline, LexMode, Lexer, SyntaxKind, SyntaxNode}; - -/// Parse a source file. -pub fn parse(text: &str) -> SyntaxNode { - let mut p = Parser::new(text, 0, LexMode::Markup); - markup(&mut p, true, 0, |_| false); - p.finish().into_iter().next().unwrap() -} - -/// Parse code directly. -/// -/// This is only used for syntax highlighting. -pub fn parse_code(text: &str) -> SyntaxNode { - let mut p = Parser::new(text, 0, LexMode::Code); - let m = p.marker(); - p.skip(); - code_exprs(&mut p, |_| false); - p.wrap_skipless(m, SyntaxKind::Code); - p.finish().into_iter().next().unwrap() -} - -fn markup( - p: &mut Parser, - mut at_start: bool, - min_indent: usize, - mut stop: impl FnMut(&Parser) -> bool, -) { - let m = p.marker(); - let mut nesting: usize = 0; - while !p.eof() { - match p.current() { - SyntaxKind::LeftBracket => nesting += 1, - SyntaxKind::RightBracket if nesting > 0 => nesting -= 1, - _ if stop(p) => break, - _ => {} - } - - if p.newline() { - at_start = true; - if min_indent > 0 && p.column(p.current_end()) < min_indent { - break; - } - p.eat(); - continue; - } - - let prev = p.prev_end(); - markup_expr(p, &mut at_start); - if !p.progress(prev) { - p.unexpected(); - } - } - p.wrap(m, SyntaxKind::Markup); -} - -pub(super) fn reparse_markup( - text: &str, - range: Range<usize>, - at_start: &mut bool, - nesting: &mut usize, - mut stop: impl FnMut(SyntaxKind) -> bool, -) -> Option<Vec<SyntaxNode>> { - let mut p = Parser::new(text, range.start, LexMode::Markup); - while !p.eof() && p.current_start() < range.end { - match p.current() { - SyntaxKind::LeftBracket => *nesting += 1, - SyntaxKind::RightBracket if *nesting > 0 => *nesting -= 1, - _ if stop(p.current()) => break, - _ => {} - } - - if p.newline() { - *at_start = true; - p.eat(); - continue; - } - - let prev = p.prev_end(); - markup_expr(&mut p, at_start); - if !p.progress(prev) { - p.unexpected(); - } - } - (p.balanced && p.current_start() == range.end).then(|| p.finish()) -} - -fn markup_expr(p: &mut Parser, at_start: &mut bool) { - match p.current() { - SyntaxKind::Space - | SyntaxKind::Parbreak - | SyntaxKind::LineComment - | SyntaxKind::BlockComment => { - p.eat(); - return; - } - - SyntaxKind::Text - | SyntaxKind::Linebreak - | SyntaxKind::Escape - | SyntaxKind::Shorthand - | SyntaxKind::SmartQuote - | SyntaxKind::Raw - | SyntaxKind::Link - | SyntaxKind::Label => p.eat(), - - SyntaxKind::Hashtag => embedded_code_expr(p), - SyntaxKind::Star => strong(p), - SyntaxKind::Underscore => emph(p), - SyntaxKind::HeadingMarker if *at_start => heading(p), - SyntaxKind::ListMarker if *at_start => list_item(p), - SyntaxKind::EnumMarker if *at_start => enum_item(p), - SyntaxKind::TermMarker if *at_start => term_item(p), - SyntaxKind::RefMarker => reference(p), - SyntaxKind::Dollar => equation(p), - - SyntaxKind::LeftBracket - | SyntaxKind::RightBracket - | SyntaxKind::HeadingMarker - | SyntaxKind::ListMarker - | SyntaxKind::EnumMarker - | SyntaxKind::TermMarker - | SyntaxKind::Colon => p.convert(SyntaxKind::Text), - - _ => {} - } - - *at_start = false; -} - -fn strong(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Star); - markup(p, false, 0, |p| { - p.at(SyntaxKind::Star) - || p.at(SyntaxKind::Parbreak) - || p.at(SyntaxKind::RightBracket) - }); - p.expect_closing_delimiter(m, SyntaxKind::Star); - p.wrap(m, SyntaxKind::Strong); -} - -fn emph(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Underscore); - markup(p, false, 0, |p| { - p.at(SyntaxKind::Underscore) - || p.at(SyntaxKind::Parbreak) - || p.at(SyntaxKind::RightBracket) - }); - p.expect_closing_delimiter(m, SyntaxKind::Underscore); - p.wrap(m, SyntaxKind::Emph); -} - -fn heading(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::HeadingMarker); - whitespace_line(p); - markup(p, false, usize::MAX, |p| { - p.at(SyntaxKind::Label) - || p.at(SyntaxKind::RightBracket) - || (p.at(SyntaxKind::Space) && p.lexer.clone().next() == SyntaxKind::Label) - }); - p.wrap(m, SyntaxKind::Heading); -} - -fn list_item(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::ListMarker); - let min_indent = p.column(p.prev_end()); - whitespace_line(p); - markup(p, false, min_indent, |p| p.at(SyntaxKind::RightBracket)); - p.wrap(m, SyntaxKind::ListItem); -} - -fn enum_item(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::EnumMarker); - let min_indent = p.column(p.prev_end()); - whitespace_line(p); - markup(p, false, min_indent, |p| p.at(SyntaxKind::RightBracket)); - p.wrap(m, SyntaxKind::EnumItem); -} - -fn term_item(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::TermMarker); - let min_indent = p.column(p.prev_end()); - whitespace_line(p); - markup(p, false, usize::MAX, |p| { - p.at(SyntaxKind::Colon) || p.at(SyntaxKind::RightBracket) - }); - p.expect(SyntaxKind::Colon); - whitespace_line(p); - markup(p, false, min_indent, |p| p.at(SyntaxKind::RightBracket)); - p.wrap(m, SyntaxKind::TermItem); -} - -fn reference(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::RefMarker); - if p.directly_at(SyntaxKind::LeftBracket) { - content_block(p); - } - p.wrap(m, SyntaxKind::Ref); -} - -fn whitespace_line(p: &mut Parser) { - while !p.newline() && p.current().is_trivia() { - p.eat(); - } -} - -fn equation(p: &mut Parser) { - let m = p.marker(); - p.enter(LexMode::Math); - p.assert(SyntaxKind::Dollar); - math(p, |p| p.at(SyntaxKind::Dollar)); - p.expect_closing_delimiter(m, SyntaxKind::Dollar); - p.exit(); - p.wrap(m, SyntaxKind::Equation); -} - -fn math(p: &mut Parser, mut stop: impl FnMut(&Parser) -> bool) { - let m = p.marker(); - while !p.eof() && !stop(p) { - let prev = p.prev_end(); - math_expr(p); - if !p.progress(prev) { - p.unexpected(); - } - } - p.wrap(m, SyntaxKind::Math); -} - -fn math_expr(p: &mut Parser) { - math_expr_prec(p, 0, SyntaxKind::Eof) -} - -fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { - let m = p.marker(); - let mut continuable = false; - match p.current() { - SyntaxKind::Hashtag => embedded_code_expr(p), - SyntaxKind::MathIdent => { - continuable = true; - p.eat(); - while p.directly_at(SyntaxKind::Text) - && p.current_text() == "." - && matches!( - p.lexer.clone().next(), - SyntaxKind::MathIdent | SyntaxKind::Text - ) - { - p.convert(SyntaxKind::Dot); - p.convert(SyntaxKind::Ident); - p.wrap(m, SyntaxKind::FieldAccess); - } - if min_prec < 3 && p.directly_at(SyntaxKind::Text) && p.current_text() == "(" - { - math_args(p); - p.wrap(m, SyntaxKind::FuncCall); - continuable = false; - } - } - - SyntaxKind::Text | SyntaxKind::Shorthand => { - continuable = matches!( - math_class(p.current_text()), - None | Some(MathClass::Alphabetic) - ); - if !maybe_delimited(p, true) { - p.eat(); - } - } - - SyntaxKind::Linebreak | SyntaxKind::MathAlignPoint => p.eat(), - SyntaxKind::Escape | SyntaxKind::Str => { - continuable = true; - p.eat(); - } - - SyntaxKind::Root => { - if min_prec < 3 { - p.eat(); - let m2 = p.marker(); - math_expr_prec(p, 2, stop); - math_unparen(p, m2); - p.wrap(m, SyntaxKind::MathRoot); - } - } - - _ => p.expected("expression"), - } - - if continuable - && min_prec < 3 - && p.prev_end() == p.current_start() - && maybe_delimited(p, false) - { - p.wrap(m, SyntaxKind::Math); - } - - while !p.eof() && !p.at(stop) { - if p.directly_at(SyntaxKind::Text) && p.current_text() == "!" { - p.eat(); - p.wrap(m, SyntaxKind::Math); - continue; - } - - let Some((kind, stop, assoc, mut prec)) = math_op(p.current()) else { - break; - }; - - if prec < min_prec { - break; - } - - match assoc { - ast::Assoc::Left => prec += 1, - ast::Assoc::Right => {} - } - - if kind == SyntaxKind::MathFrac { - math_unparen(p, m); - } - - p.eat(); - let m2 = p.marker(); - math_expr_prec(p, prec, stop); - math_unparen(p, m2); - - if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) { - let m3 = p.marker(); - math_expr_prec(p, prec, SyntaxKind::Eof); - math_unparen(p, m3); - } - - p.wrap(m, kind); - } -} - -fn maybe_delimited(p: &mut Parser, allow_fence: bool) -> bool { - if allow_fence && math_class(p.current_text()) == Some(MathClass::Fence) { - math_delimited(p, MathClass::Fence); - true - } else if math_class(p.current_text()) == Some(MathClass::Opening) { - math_delimited(p, MathClass::Closing); - true - } else { - false - } -} - -fn math_delimited(p: &mut Parser, stop: MathClass) { - let m = p.marker(); - p.eat(); - let m2 = p.marker(); - while !p.eof() && !p.at(SyntaxKind::Dollar) { - let class = math_class(p.current_text()); - if stop == MathClass::Fence && class == Some(MathClass::Closing) { - break; - } - - if class == Some(stop) { - p.wrap(m2, SyntaxKind::Math); - p.eat(); - p.wrap(m, SyntaxKind::MathDelimited); - return; - } - - let prev = p.prev_end(); - math_expr(p); - if !p.progress(prev) { - p.unexpected(); - } - } - - p.wrap(m, SyntaxKind::Math); -} - -fn math_unparen(p: &mut Parser, m: Marker) { - let Some(node) = p.nodes.get_mut(m.0) else { return }; - if node.kind() != SyntaxKind::MathDelimited { - return; - } - - if let [first, .., last] = node.children_mut() { - if first.text() == "(" && last.text() == ")" { - first.convert_to_kind(SyntaxKind::LeftParen); - last.convert_to_kind(SyntaxKind::RightParen); - } - } - - node.convert_to_kind(SyntaxKind::Math); -} - -fn math_class(text: &str) -> Option<MathClass> { - match text { - "[|" => return Some(MathClass::Opening), - "|]" => return Some(MathClass::Closing), - "||" => return Some(MathClass::Fence), - _ => {} - } - - let mut chars = text.chars(); - chars - .next() - .filter(|_| chars.next().is_none()) - .and_then(unicode_math_class::class) -} - -fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> { - match kind { - SyntaxKind::Underscore => { - Some((SyntaxKind::MathAttach, SyntaxKind::Hat, ast::Assoc::Right, 2)) - } - SyntaxKind::Hat => { - Some((SyntaxKind::MathAttach, SyntaxKind::Underscore, ast::Assoc::Right, 2)) - } - SyntaxKind::Slash => { - Some((SyntaxKind::MathFrac, SyntaxKind::Eof, ast::Assoc::Left, 1)) - } - _ => None, - } -} - -fn math_args(p: &mut Parser) { - let m = p.marker(); - p.convert(SyntaxKind::LeftParen); - - let mut namable = true; - let mut named = None; - let mut has_arrays = false; - let mut array = p.marker(); - let mut arg = p.marker(); - - while !p.eof() && !p.at(SyntaxKind::Dollar) { - if namable - && (p.at(SyntaxKind::MathIdent) || p.at(SyntaxKind::Text)) - && p.text[p.current_end()..].starts_with(':') - { - p.convert(SyntaxKind::Ident); - p.convert(SyntaxKind::Colon); - named = Some(arg); - arg = p.marker(); - array = p.marker(); - } - - match p.current_text() { - ")" => break, - ";" => { - maybe_wrap_in_math(p, arg, named); - p.wrap(array, SyntaxKind::Array); - p.convert(SyntaxKind::Semicolon); - array = p.marker(); - arg = p.marker(); - namable = true; - named = None; - has_arrays = true; - continue; - } - "," => { - maybe_wrap_in_math(p, arg, named); - p.convert(SyntaxKind::Comma); - arg = p.marker(); - namable = true; - if named.is_some() { - array = p.marker(); - named = None; - } - continue; - } - _ => {} - } - - let prev = p.prev_end(); - math_expr(p); - if !p.progress(prev) { - p.unexpected(); - } - - namable = false; - } - - if arg != p.marker() { - maybe_wrap_in_math(p, arg, named); - if named.is_some() { - array = p.marker(); - } - } - - if has_arrays && array != p.marker() { - p.wrap(array, SyntaxKind::Array); - } - - if p.at(SyntaxKind::Text) && p.current_text() == ")" { - p.convert(SyntaxKind::RightParen); - } else { - p.expected("closing paren"); - p.balanced = false; - } - - p.wrap(m, SyntaxKind::Args); -} - -fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, named: Option<Marker>) { - let exprs = p.post_process(arg).filter(|node| node.is::<ast::Expr>()).count(); - if exprs != 1 { - p.wrap(arg, SyntaxKind::Math); - } - - if let Some(m) = named { - p.wrap(m, SyntaxKind::Named); - } -} - -fn code(p: &mut Parser, stop: impl FnMut(&Parser) -> bool) { - let m = p.marker(); - code_exprs(p, stop); - p.wrap(m, SyntaxKind::Code); -} - -fn code_exprs(p: &mut Parser, mut stop: impl FnMut(&Parser) -> bool) { - while !p.eof() && !stop(p) { - p.stop_at_newline(true); - let prev = p.prev_end(); - code_expr(p); - if p.progress(prev) && !p.eof() && !stop(p) && !p.eat_if(SyntaxKind::Semicolon) { - p.expected("semicolon or line break"); - } - p.unstop(); - if !p.progress(prev) && !p.eof() { - p.unexpected(); - } - } -} - -fn code_expr(p: &mut Parser) { - code_expr_prec(p, false, 0, false) -} - -fn code_expr_or_pattern(p: &mut Parser) { - code_expr_prec(p, false, 0, true) -} - -fn embedded_code_expr(p: &mut Parser) { - p.stop_at_newline(true); - p.enter(LexMode::Code); - p.assert(SyntaxKind::Hashtag); - p.unskip(); - - let stmt = matches!( - p.current(), - SyntaxKind::Let - | SyntaxKind::Set - | SyntaxKind::Show - | SyntaxKind::Import - | SyntaxKind::Include - ); - - let prev = p.prev_end(); - code_expr_prec(p, true, 0, false); - - // Consume error for things like `#12p` or `#"abc\"`. - if !p.progress(prev) { - p.unexpected(); - } - - let semi = - (stmt || p.directly_at(SyntaxKind::Semicolon)) && p.eat_if(SyntaxKind::Semicolon); - - if stmt && !semi && !p.eof() && !p.at(SyntaxKind::RightBracket) { - p.expected("semicolon or line break"); - } - - p.exit(); - p.unstop(); -} - -fn code_expr_prec( - p: &mut Parser, - atomic: bool, - min_prec: usize, - allow_destructuring: bool, -) { - let m = p.marker(); - if let (false, Some(op)) = (atomic, ast::UnOp::from_kind(p.current())) { - p.eat(); - code_expr_prec(p, atomic, op.precedence(), false); - p.wrap(m, SyntaxKind::Unary); - } else { - code_primary(p, atomic, allow_destructuring); - } - - loop { - if p.directly_at(SyntaxKind::LeftParen) || p.directly_at(SyntaxKind::LeftBracket) - { - args(p); - p.wrap(m, SyntaxKind::FuncCall); - continue; - } - - let at_field_or_method = - p.directly_at(SyntaxKind::Dot) && p.lexer.clone().next() == SyntaxKind::Ident; - - if atomic && !at_field_or_method { - break; - } - - if p.eat_if(SyntaxKind::Dot) { - p.expect(SyntaxKind::Ident); - p.wrap(m, SyntaxKind::FieldAccess); - continue; - } - - let binop = - if ast::BinOp::NotIn.precedence() >= min_prec && p.eat_if(SyntaxKind::Not) { - if p.at(SyntaxKind::In) { - Some(ast::BinOp::NotIn) - } else { - p.expected("keyword `in`"); - break; - } - } else { - ast::BinOp::from_kind(p.current()) - }; - - if let Some(op) = binop { - let mut prec = op.precedence(); - if prec < min_prec { - break; - } - - match op.assoc() { - ast::Assoc::Left => prec += 1, - ast::Assoc::Right => {} - } - - p.eat(); - code_expr_prec(p, false, prec, false); - p.wrap(m, SyntaxKind::Binary); - continue; - } - - break; - } -} - -fn code_primary(p: &mut Parser, atomic: bool, allow_destructuring: bool) { - let m = p.marker(); - match p.current() { - SyntaxKind::Ident => { - p.eat(); - if !atomic && p.at(SyntaxKind::Arrow) { - p.wrap(m, SyntaxKind::Params); - p.assert(SyntaxKind::Arrow); - code_expr(p); - p.wrap(m, SyntaxKind::Closure); - } - } - SyntaxKind::Underscore if !atomic => { - p.eat(); - if p.at(SyntaxKind::Arrow) { - p.wrap(m, SyntaxKind::Params); - p.eat(); - code_expr(p); - p.wrap(m, SyntaxKind::Closure); - } else if let Some(underscore) = p.node_mut(m) { - underscore.convert_to_error("expected expression, found underscore"); - } - } - - SyntaxKind::LeftBrace => code_block(p), - SyntaxKind::LeftBracket => content_block(p), - SyntaxKind::LeftParen => with_paren(p, allow_destructuring), - SyntaxKind::Dollar => equation(p), - SyntaxKind::Let => let_binding(p), - SyntaxKind::Set => set_rule(p), - SyntaxKind::Show => show_rule(p), - SyntaxKind::If => conditional(p), - SyntaxKind::While => while_loop(p), - SyntaxKind::For => for_loop(p), - SyntaxKind::Import => module_import(p), - SyntaxKind::Include => module_include(p), - SyntaxKind::Break => break_stmt(p), - SyntaxKind::Continue => continue_stmt(p), - SyntaxKind::Return => return_stmt(p), - - SyntaxKind::None - | SyntaxKind::Auto - | SyntaxKind::Int - | SyntaxKind::Float - | SyntaxKind::Bool - | SyntaxKind::Numeric - | SyntaxKind::Str - | SyntaxKind::Label - | SyntaxKind::Raw => p.eat(), - - _ => p.expected("expression"), - } -} - -fn block(p: &mut Parser) { - match p.current() { - SyntaxKind::LeftBracket => content_block(p), - SyntaxKind::LeftBrace => code_block(p), - _ => p.expected("block"), - } -} - -pub(super) fn reparse_block(text: &str, range: Range<usize>) -> Option<SyntaxNode> { - let mut p = Parser::new(text, range.start, LexMode::Code); - assert!(p.at(SyntaxKind::LeftBracket) || p.at(SyntaxKind::LeftBrace)); - block(&mut p); - (p.balanced && p.prev_end() == range.end) - .then(|| p.finish().into_iter().next().unwrap()) -} - -fn code_block(p: &mut Parser) { - let m = p.marker(); - p.enter(LexMode::Code); - p.stop_at_newline(false); - p.assert(SyntaxKind::LeftBrace); - code(p, |p| { - p.at(SyntaxKind::RightBrace) - || p.at(SyntaxKind::RightBracket) - || p.at(SyntaxKind::RightParen) - }); - p.expect_closing_delimiter(m, SyntaxKind::RightBrace); - p.exit(); - p.unstop(); - p.wrap(m, SyntaxKind::CodeBlock); -} - -fn content_block(p: &mut Parser) { - let m = p.marker(); - p.enter(LexMode::Markup); - p.assert(SyntaxKind::LeftBracket); - markup(p, true, 0, |p| p.at(SyntaxKind::RightBracket)); - p.expect_closing_delimiter(m, SyntaxKind::RightBracket); - p.exit(); - p.wrap(m, SyntaxKind::ContentBlock); -} - -fn with_paren(p: &mut Parser, allow_destructuring: bool) { - let m = p.marker(); - let mut kind = collection(p, true); - if p.at(SyntaxKind::Arrow) { - validate_params_at(p, m); - p.wrap(m, SyntaxKind::Params); - p.assert(SyntaxKind::Arrow); - code_expr(p); - kind = SyntaxKind::Closure; - } else if p.at(SyntaxKind::Eq) && kind != SyntaxKind::Parenthesized { - // TODO: add warning if p.at(SyntaxKind::Eq) && kind == SyntaxKind::Parenthesized - - validate_pattern_at(p, m, false); - p.wrap(m, SyntaxKind::Destructuring); - p.assert(SyntaxKind::Eq); - code_expr(p); - kind = SyntaxKind::DestructAssignment; - } - - match kind { - SyntaxKind::Array if !allow_destructuring => validate_array_at(p, m), - SyntaxKind::Dict if !allow_destructuring => validate_dict_at(p, m), - SyntaxKind::Parenthesized if !allow_destructuring => { - validate_parenthesized_at(p, m) - } - SyntaxKind::Destructuring if !allow_destructuring => { - invalidate_destructuring(p, m) - } - _ => {} - } - p.wrap(m, kind); -} - -fn invalidate_destructuring(p: &mut Parser, m: Marker) { - let mut collection_kind = Option::None; - for child in p.post_process(m) { - match child.kind() { - SyntaxKind::Named | SyntaxKind::Keyed => match collection_kind { - Some(SyntaxKind::Array) => child.convert_to_error(eco_format!( - "expected expression, found {}", - child.kind().name() - )), - _ => collection_kind = Some(SyntaxKind::Dict), - }, - SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {} - kind => match collection_kind { - Some(SyntaxKind::Dict) => child.convert_to_error(eco_format!( - "expected named or keyed pair, found {}", - kind.name() - )), - _ => collection_kind = Some(SyntaxKind::Array), - }, - } - } -} - -fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind { - p.stop_at_newline(false); - - let m = p.marker(); - p.assert(SyntaxKind::LeftParen); - - let mut count = 0; - let mut parenthesized = true; - let mut kind = None; - if keyed && p.eat_if(SyntaxKind::Colon) { - kind = Some(SyntaxKind::Dict); - parenthesized = false; - } - - while !p.current().is_terminator() { - let prev = p.prev_end(); - match item(p, keyed) { - SyntaxKind::Spread => parenthesized = false, - SyntaxKind::Named | SyntaxKind::Keyed => { - match kind { - Some(SyntaxKind::Array) => kind = Some(SyntaxKind::Destructuring), - _ => kind = Some(SyntaxKind::Dict), - } - parenthesized = false; - } - SyntaxKind::Int => match kind { - Some(SyntaxKind::Array) | None => kind = Some(SyntaxKind::Array), - Some(_) => kind = Some(SyntaxKind::Destructuring), - }, - _ if kind.is_none() => kind = Some(SyntaxKind::Array), - _ => {} - } - - if !p.progress(prev) { - p.unexpected(); - continue; - } - - count += 1; - - if p.current().is_terminator() { - break; - } - - if p.expect(SyntaxKind::Comma) { - parenthesized = false; - } - } - - p.expect_closing_delimiter(m, SyntaxKind::RightParen); - p.unstop(); - - if parenthesized && count == 1 { - SyntaxKind::Parenthesized - } else { - kind.unwrap_or(SyntaxKind::Array) - } -} - -fn item(p: &mut Parser, keyed: bool) -> SyntaxKind { - let m = p.marker(); - - if p.eat_if(SyntaxKind::Dots) { - if p.at(SyntaxKind::Comma) || p.at(SyntaxKind::RightParen) { - p.wrap(m, SyntaxKind::Spread); - return SyntaxKind::Spread; - } - - code_expr(p); - p.wrap(m, SyntaxKind::Spread); - return SyntaxKind::Spread; - } - - if p.at(SyntaxKind::Underscore) { - // This is a temporary workaround to fix `v.map(_ => {})`. - let mut lexer = p.lexer.clone(); - let next = - std::iter::from_fn(|| Some(lexer.next())).find(|kind| !kind.is_trivia()); - if next != Some(SyntaxKind::Arrow) { - p.eat(); - return SyntaxKind::Underscore; - } - } - - code_expr_or_pattern(p); - - if !p.eat_if(SyntaxKind::Colon) { - return SyntaxKind::Int; - } - - if !p.eat_if(SyntaxKind::Underscore) { - code_expr(p); - } - - let kind = match p.node(m).map(SyntaxNode::kind) { - Some(SyntaxKind::Ident) => SyntaxKind::Named, - Some(SyntaxKind::Str) if keyed => SyntaxKind::Keyed, - _ => { - for child in p.post_process(m) { - if child.kind() == SyntaxKind::Colon { - break; - } - - let mut message = EcoString::from("expected identifier"); - if keyed { - message.push_str(" or string"); - } - message.push_str(", found "); - message.push_str(child.kind().name()); - child.convert_to_error(message); - } - SyntaxKind::Named - } - }; - - p.wrap(m, kind); - kind -} - -fn args(p: &mut Parser) { - if !p.at(SyntaxKind::LeftParen) && !p.at(SyntaxKind::LeftBracket) { - p.expected("argument list"); - } - - let m = p.marker(); - if p.at(SyntaxKind::LeftParen) { - collection(p, false); - validate_args_at(p, m); - } - - while p.directly_at(SyntaxKind::LeftBracket) { - content_block(p); - } - - p.wrap(m, SyntaxKind::Args); -} - -enum PatternKind { - Ident, - Placeholder, - Destructuring, -} - -fn pattern(p: &mut Parser) -> PatternKind { - let m = p.marker(); - if p.at(SyntaxKind::LeftParen) { - let kind = collection(p, false); - validate_pattern_at(p, m, true); - - if kind == SyntaxKind::Parenthesized { - PatternKind::Ident - } else { - p.wrap(m, SyntaxKind::Destructuring); - PatternKind::Destructuring - } - } else if p.eat_if(SyntaxKind::Underscore) { - PatternKind::Placeholder - } else { - p.expect(SyntaxKind::Ident); - PatternKind::Ident - } -} - -fn let_binding(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Let); - - let m2 = p.marker(); - let mut closure = false; - let mut destructuring = false; - match pattern(p) { - PatternKind::Ident => { - closure = p.directly_at(SyntaxKind::LeftParen); - if closure { - let m3 = p.marker(); - collection(p, false); - validate_params_at(p, m3); - p.wrap(m3, SyntaxKind::Params); - } - } - PatternKind::Placeholder => {} - PatternKind::Destructuring => destructuring = true, - } - - let f = if closure || destructuring { Parser::expect } else { Parser::eat_if }; - if f(p, SyntaxKind::Eq) { - code_expr(p); - } - - if closure { - p.wrap(m2, SyntaxKind::Closure); - } - - p.wrap(m, SyntaxKind::LetBinding); -} - -fn set_rule(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Set); - - let m2 = p.marker(); - p.expect(SyntaxKind::Ident); - while p.eat_if(SyntaxKind::Dot) { - p.expect(SyntaxKind::Ident); - p.wrap(m2, SyntaxKind::FieldAccess); - } - - args(p); - if p.eat_if(SyntaxKind::If) { - code_expr(p); - } - p.wrap(m, SyntaxKind::SetRule); -} - -fn show_rule(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Show); - p.unskip(); - let m2 = p.marker(); - p.skip(); - - if !p.at(SyntaxKind::Colon) { - code_expr(p); - } - - if p.eat_if(SyntaxKind::Colon) { - code_expr(p); - } else { - p.expected_at(m2, "colon"); - } - - p.wrap(m, SyntaxKind::ShowRule); -} - -fn conditional(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::If); - code_expr(p); - block(p); - if p.eat_if(SyntaxKind::Else) { - if p.at(SyntaxKind::If) { - conditional(p); - } else { - block(p); - } - } - p.wrap(m, SyntaxKind::Conditional); -} - -fn while_loop(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::While); - code_expr(p); - block(p); - p.wrap(m, SyntaxKind::WhileLoop); -} - -fn for_loop(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::For); - pattern(p); - if p.at(SyntaxKind::Comma) { - p.expected("keyword `in` - did you mean to use a destructuring pattern?"); - if !p.eat_if(SyntaxKind::Ident) { - p.eat_if(SyntaxKind::Underscore); - } - p.eat_if(SyntaxKind::In); - } else { - p.expect(SyntaxKind::In); - } - code_expr(p); - block(p); - p.wrap(m, SyntaxKind::ForLoop); -} - -fn module_import(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Import); - code_expr(p); - if p.eat_if(SyntaxKind::Colon) && !p.eat_if(SyntaxKind::Star) { - import_items(p); - } - p.wrap(m, SyntaxKind::ModuleImport); -} - -fn import_items(p: &mut Parser) { - let m = p.marker(); - while !p.eof() && !p.at(SyntaxKind::Semicolon) { - if !p.eat_if(SyntaxKind::Ident) { - p.unexpected(); - } - if p.current().is_terminator() { - break; - } - p.expect(SyntaxKind::Comma); - } - p.wrap(m, SyntaxKind::ImportItems); -} - -fn module_include(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Include); - code_expr(p); - p.wrap(m, SyntaxKind::ModuleInclude); -} - -fn break_stmt(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Break); - p.wrap(m, SyntaxKind::LoopBreak); -} - -fn continue_stmt(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Continue); - p.wrap(m, SyntaxKind::LoopContinue); -} - -fn return_stmt(p: &mut Parser) { - let m = p.marker(); - p.assert(SyntaxKind::Return); - if !p.current().is_terminator() && !p.at(SyntaxKind::Comma) { - code_expr(p); - } - p.wrap(m, SyntaxKind::FuncReturn); -} - -fn validate_parenthesized_at(p: &mut Parser, m: Marker) { - for child in p.post_process(m) { - let kind = child.kind(); - match kind { - SyntaxKind::Array => validate_array(child.children_mut().iter_mut()), - SyntaxKind::Dict => validate_dict(child.children_mut().iter_mut()), - SyntaxKind::Underscore => { - child.convert_to_error(eco_format!( - "expected expression, found {}", - kind.name() - )); - } - _ => {} - } - } -} - -fn validate_array_at(p: &mut Parser, m: Marker) { - validate_array(p.post_process(m)) -} - -fn validate_array<'a>(children: impl Iterator<Item = &'a mut SyntaxNode>) { - for child in children { - let kind = child.kind(); - match kind { - SyntaxKind::Array => validate_array(child.children_mut().iter_mut()), - SyntaxKind::Dict => validate_dict(child.children_mut().iter_mut()), - SyntaxKind::Named | SyntaxKind::Keyed | SyntaxKind::Underscore => { - child.convert_to_error(eco_format!( - "expected expression, found {}", - kind.name() - )); - } - _ => {} - } - } -} - -fn validate_dict_at(p: &mut Parser, m: Marker) { - validate_dict(p.post_process(m)) -} - -fn validate_dict<'a>(children: impl Iterator<Item = &'a mut SyntaxNode>) { - let mut used = HashSet::new(); - for child in children { - match child.kind() { - SyntaxKind::Named | SyntaxKind::Keyed => { - let Some(first) = child.children_mut().first_mut() else { continue }; - let key = match first.cast::<ast::Str>() { - Some(str) => str.get(), - None => first.text().clone(), - }; - - if !used.insert(key.clone()) { - first.convert_to_error(eco_format!("duplicate key: {key}")); - child.make_erroneous(); - } - } - SyntaxKind::Spread => {} - SyntaxKind::LeftParen - | SyntaxKind::RightParen - | SyntaxKind::Comma - | SyntaxKind::Colon => {} - kind => { - child.convert_to_error(eco_format!( - "expected named or keyed pair, found {}", - kind.name() - )); - } - } - } -} - -fn validate_params_at(p: &mut Parser, m: Marker) { - let mut used_spread = false; - let mut used = HashSet::new(); - for child in p.post_process(m) { - match child.kind() { - SyntaxKind::Ident => { - if !used.insert(child.text().clone()) { - child.convert_to_error(eco_format!( - "duplicate parameter: {}", - child.text() - )); - } - } - SyntaxKind::Named => { - let Some(within) = child.children_mut().first_mut() else { return }; - if !used.insert(within.text().clone()) { - within.convert_to_error(eco_format!( - "duplicate parameter: {}", - within.text() - )); - child.make_erroneous(); - } - } - SyntaxKind::Spread => { - let Some(within) = child.children_mut().last_mut() else { continue }; - if used_spread { - child.convert_to_error("only one argument sink is allowed"); - continue; - } - used_spread = true; - if within.kind() == SyntaxKind::Dots { - continue; - } else if within.kind() != SyntaxKind::Ident { - within.convert_to_error(eco_format!( - "expected identifier, found {}", - within.kind().name(), - )); - child.make_erroneous(); - continue; - } - if !used.insert(within.text().clone()) { - within.convert_to_error(eco_format!( - "duplicate parameter: {}", - within.text() - )); - child.make_erroneous(); - } - } - SyntaxKind::Array | SyntaxKind::Dict | SyntaxKind::Destructuring => { - validate_pattern(child.children_mut().iter_mut(), &mut used, false); - child.convert_to_kind(SyntaxKind::Destructuring); - } - SyntaxKind::LeftParen - | SyntaxKind::RightParen - | SyntaxKind::Comma - | SyntaxKind::Underscore => {} - kind => { - child.convert_to_error(eco_format!( - "expected identifier, named pair or argument sink, found {}", - kind.name() - )); - } - } - } -} - -fn validate_args_at(p: &mut Parser, m: Marker) { - let mut used = HashSet::new(); - for child in p.post_process(m) { - if child.kind() == SyntaxKind::Named { - let Some(within) = child.children_mut().first_mut() else { return }; - if !used.insert(within.text().clone()) { - within.convert_to_error(eco_format!( - "duplicate argument: {}", - within.text() - )); - child.make_erroneous(); - } - } else if child.kind() == SyntaxKind::Underscore { - child.convert_to_error("unexpected underscore"); - } - } -} - -fn validate_pattern_at(p: &mut Parser, m: Marker, forbid_expressions: bool) { - let mut used = HashSet::new(); - validate_pattern(p.post_process(m), &mut used, forbid_expressions); -} - -fn validate_pattern<'a>( - children: impl Iterator<Item = &'a mut SyntaxNode>, - used: &mut HashSet<EcoString>, - forbid_expressions: bool, -) { - let mut used_spread = false; - for child in children { - match child.kind() { - SyntaxKind::Ident => { - if !used.insert(child.text().clone()) { - child.convert_to_error( - "at most one binding per identifier is allowed", - ); - } - } - SyntaxKind::Spread => { - let Some(within) = child.children_mut().last_mut() else { continue }; - if used_spread { - child.convert_to_error("at most one destructuring sink is allowed"); - continue; - } - used_spread = true; - - if within.kind() == SyntaxKind::Dots { - continue; - } else if forbid_expressions && within.kind() != SyntaxKind::Ident { - within.convert_to_error(eco_format!( - "expected identifier, found {}", - within.kind().name(), - )); - child.make_erroneous(); - continue; - } - - if !used.insert(within.text().clone()) { - within.convert_to_error( - "at most one binding per identifier is allowed", - ); - child.make_erroneous(); - } - } - SyntaxKind::Named => { - let Some(within) = child.children_mut().first_mut() else { return }; - if !used.insert(within.text().clone()) { - within.convert_to_error( - "at most one binding per identifier is allowed", - ); - child.make_erroneous(); - } - - if forbid_expressions { - let Some(within) = child.children_mut().last_mut() else { return }; - if within.kind() != SyntaxKind::Ident - && within.kind() != SyntaxKind::Underscore - { - within.convert_to_error(eco_format!( - "expected identifier, found {}", - within.kind().name(), - )); - child.make_erroneous(); - } - } - } - SyntaxKind::LeftParen - | SyntaxKind::RightParen - | SyntaxKind::Comma - | SyntaxKind::Underscore => {} - kind => { - if forbid_expressions { - child.convert_to_error(eco_format!( - "expected identifier or destructuring sink, found {}", - kind.name() - )); - } - } - } - } -} - -/// Manages parsing of a stream of tokens. -struct Parser<'s> { - text: &'s str, - lexer: Lexer<'s>, - prev_end: usize, - current_start: usize, - current: SyntaxKind, - modes: Vec<LexMode>, - nodes: Vec<SyntaxNode>, - stop_at_newline: Vec<bool>, - balanced: bool, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -struct Marker(usize); - -impl<'s> Parser<'s> { - fn new(text: &'s str, offset: usize, mode: LexMode) -> Self { - let mut lexer = Lexer::new(text, mode); - lexer.jump(offset); - let current = lexer.next(); - Self { - lexer, - text, - prev_end: offset, - current_start: offset, - current, - modes: vec![], - nodes: vec![], - stop_at_newline: vec![], - balanced: true, - } - } - - fn finish(self) -> Vec<SyntaxNode> { - self.nodes - } - - fn prev_end(&self) -> usize { - self.prev_end - } - - fn current(&self) -> SyntaxKind { - self.current - } - - fn current_start(&self) -> usize { - self.current_start - } - - fn current_end(&self) -> usize { - self.lexer.cursor() - } - - fn current_text(&self) -> &'s str { - &self.text[self.current_start..self.current_end()] - } - - fn at(&self, kind: SyntaxKind) -> bool { - self.current == kind - } - - #[track_caller] - fn assert(&mut self, kind: SyntaxKind) { - assert_eq!(self.current, kind); - self.eat(); - } - - fn eof(&self) -> bool { - self.at(SyntaxKind::Eof) - } - - fn directly_at(&self, kind: SyntaxKind) -> bool { - self.current == kind && self.prev_end == self.current_start - } - - fn eat_if(&mut self, kind: SyntaxKind) -> bool { - let at = self.at(kind); - if at { - self.eat(); - } - at - } - - fn convert(&mut self, kind: SyntaxKind) { - self.current = kind; - self.eat(); - } - - fn newline(&mut self) -> bool { - self.lexer.newline() - } - - fn column(&self, at: usize) -> usize { - self.text[..at].chars().rev().take_while(|&c| !is_newline(c)).count() - } - - fn marker(&self) -> Marker { - Marker(self.nodes.len()) - } - - fn node(&self, m: Marker) -> Option<&SyntaxNode> { - self.nodes.get(m.0) - } - - fn node_mut(&mut self, m: Marker) -> Option<&mut SyntaxNode> { - self.nodes.get_mut(m.0) - } - - fn post_process(&mut self, m: Marker) -> impl Iterator<Item = &mut SyntaxNode> { - self.nodes[m.0..] - .iter_mut() - .filter(|child| !child.kind().is_error() && !child.kind().is_trivia()) - } - - fn wrap(&mut self, m: Marker, kind: SyntaxKind) { - self.unskip(); - self.wrap_skipless(m, kind); - self.skip(); - } - - fn wrap_skipless(&mut self, m: Marker, kind: SyntaxKind) { - let from = m.0.min(self.nodes.len()); - let children = self.nodes.drain(from..).collect(); - self.nodes.push(SyntaxNode::inner(kind, children)); - } - - fn progress(&self, offset: usize) -> bool { - offset < self.prev_end - } - - fn enter(&mut self, mode: LexMode) { - self.modes.push(self.lexer.mode()); - self.lexer.set_mode(mode); - } - - fn exit(&mut self) { - let mode = self.modes.pop().unwrap(); - if mode != self.lexer.mode() { - self.unskip(); - self.lexer.set_mode(mode); - self.lexer.jump(self.current_start); - self.lex(); - self.skip(); - } - } - - fn stop_at_newline(&mut self, stop: bool) { - self.stop_at_newline.push(stop); - } - - fn unstop(&mut self) { - self.unskip(); - self.stop_at_newline.pop(); - self.lexer.jump(self.prev_end); - self.lex(); - self.skip(); - } - - fn eat(&mut self) { - self.save(); - self.lex(); - self.skip(); - } - - fn skip(&mut self) { - if self.lexer.mode() != LexMode::Markup { - while self.current.is_trivia() { - self.save(); - self.lex(); - } - } - } - - fn unskip(&mut self) { - if self.lexer.mode() != LexMode::Markup && self.prev_end != self.current_start { - while self.nodes.last().map_or(false, |last| last.kind().is_trivia()) { - self.nodes.pop(); - } - - self.lexer.jump(self.prev_end); - self.lex(); - } - } - - fn save(&mut self) { - let text = self.current_text(); - if self.at(SyntaxKind::Error) { - let message = self.lexer.take_error().unwrap(); - self.nodes.push(SyntaxNode::error(message, text)); - } else { - self.nodes.push(SyntaxNode::leaf(self.current, text)); - } - - if self.lexer.mode() == LexMode::Markup || !self.current.is_trivia() { - self.prev_end = self.current_end(); - } - } - - fn lex(&mut self) { - self.current_start = self.lexer.cursor(); - self.current = self.lexer.next(); - if self.lexer.mode() == LexMode::Code - && self.lexer.newline() - && self.stop_at_newline.last().copied().unwrap_or(false) - && !matches!(self.lexer.clone().next(), SyntaxKind::Else | SyntaxKind::Dot) - { - self.current = SyntaxKind::Eof; - } - } - - fn expect(&mut self, kind: SyntaxKind) -> bool { - let at = self.at(kind); - if at { - self.eat(); - } else { - self.balanced &= !kind.is_grouping(); - self.expected(kind.name()); - } - at - } - - fn expect_closing_delimiter(&mut self, open: Marker, kind: SyntaxKind) { - if !self.eat_if(kind) { - self.nodes[open.0].convert_to_error("unclosed delimiter"); - } - } - - fn expected(&mut self, thing: &str) { - self.unskip(); - if self - .nodes - .last() - .map_or(true, |child| child.kind() != SyntaxKind::Error) - { - let message = eco_format!("expected {}", thing); - self.nodes.push(SyntaxNode::error(message, "")); - } - self.skip(); - } - - fn expected_at(&mut self, m: Marker, thing: &str) { - let message = eco_format!("expected {}", thing); - let error = SyntaxNode::error(message, ""); - self.nodes.insert(m.0, error); - } - - fn unexpected(&mut self) { - self.unskip(); - while self - .nodes - .last() - .map_or(false, |child| child.kind() == SyntaxKind::Error && child.is_empty()) - { - self.nodes.pop(); - } - self.skip(); - - let kind = self.current; - let offset = self.nodes.len(); - self.eat(); - self.balanced &= !kind.is_grouping(); - - if !kind.is_error() { - self.nodes[offset] - .convert_to_error(eco_format!("unexpected {}", kind.name())); - } - } -} diff --git a/src/syntax/reparser.rs b/src/syntax/reparser.rs deleted file mode 100644 index a4186fa7..00000000 --- a/src/syntax/reparser.rs +++ /dev/null @@ -1,322 +0,0 @@ -use std::ops::Range; - -use super::{ - is_newline, parse, reparse_block, reparse_markup, Span, SyntaxKind, SyntaxNode, -}; - -/// Refresh the given syntax node with as little parsing as possible. -/// -/// Takes the new text, the range in the old text that was replaced and the -/// length of the replacement and returns the range in the new text that was -/// ultimately reparsed. -/// -/// The high-level API for this function is -/// [`Source::edit`](super::Source::edit). -pub fn reparse( - root: &mut SyntaxNode, - text: &str, - replaced: Range<usize>, - replacement_len: usize, -) -> Range<usize> { - try_reparse(text, replaced, replacement_len, None, root, 0).unwrap_or_else(|| { - let id = root.span().id(); - *root = parse(text); - root.numberize(id, Span::FULL).unwrap(); - 0..text.len() - }) -} - -/// Try to reparse inside the given node. -fn try_reparse( - text: &str, - replaced: Range<usize>, - replacement_len: usize, - parent_kind: Option<SyntaxKind>, - node: &mut SyntaxNode, - offset: usize, -) -> Option<Range<usize>> { - // The range of children which overlap with the edit. - #[allow(clippy::reversed_empty_ranges)] - let mut overlap = usize::MAX..0; - let mut cursor = offset; - let node_kind = node.kind(); - - for (i, child) in node.children_mut().iter_mut().enumerate() { - let prev_range = cursor..cursor + child.len(); - let prev_len = child.len(); - let prev_desc = child.descendants(); - - // Does the child surround the edit? - // If so, try to reparse within it or itself. - if !child.is_leaf() && includes(&prev_range, &replaced) { - let new_len = prev_len + replacement_len - replaced.len(); - let new_range = cursor..cursor + new_len; - - // Try to reparse within the child. - if let Some(range) = try_reparse( - text, - replaced.clone(), - replacement_len, - Some(node_kind), - child, - cursor, - ) { - assert_eq!(child.len(), new_len); - let new_desc = child.descendants(); - node.update_parent(prev_len, new_len, prev_desc, new_desc); - return Some(range); - } - - // If the child is a block, try to reparse the block. - if child.kind().is_block() { - if let Some(newborn) = reparse_block(text, new_range.clone()) { - return node - .replace_children(i..i + 1, vec![newborn]) - .is_ok() - .then_some(new_range); - } - } - } - - // Does the child overlap with the edit? - if overlaps(&prev_range, &replaced) { - overlap.start = overlap.start.min(i); - overlap.end = i + 1; - } - - // Is the child beyond the edit? - if replaced.end < cursor { - break; - } - - cursor += child.len(); - } - - // Try to reparse a range of markup expressions within markup. This is only - // possible if the markup is top-level or contained in a block, not if it is - // contained in things like headings or lists because too much can go wrong - // with indent and line breaks. - if overlap.is_empty() - || node.kind() != SyntaxKind::Markup - || !matches!(parent_kind, None | Some(SyntaxKind::ContentBlock)) - { - return None; - } - - let children = node.children_mut(); - - // Reparse a segment. Retries until it works, taking exponentially more - // children into account. - let mut expansion = 1; - loop { - // Add slack in both directions. - let mut start = overlap.start.saturating_sub(expansion.max(2)); - let mut end = (overlap.end + expansion).min(children.len()); - - // Expand to the left. - while start > 0 && expand(&children[start]) { - start -= 1; - } - - // Expand to the right. - while end < children.len() && expand(&children[end]) { - end += 1; - } - - // Also take hashtag. - if start > 0 && children[start - 1].kind() == SyntaxKind::Hashtag { - start -= 1; - } - - // Synthesize what `at_start` and `nesting` would be at the start of the - // reparse. - let mut prefix_len = 0; - let mut nesting = 0; - let mut at_start = true; - for child in &children[..start] { - prefix_len += child.len(); - next_at_start(child, &mut at_start); - next_nesting(child, &mut nesting); - } - - // Determine what `at_start` will have to be at the end of the reparse. - let mut prev_len = 0; - let mut prev_at_start_after = at_start; - let mut prev_nesting_after = nesting; - for child in &children[start..end] { - prev_len += child.len(); - next_at_start(child, &mut prev_at_start_after); - next_nesting(child, &mut prev_nesting_after); - } - - // Determine the range in the new text that we want to reparse. - let shifted = offset + prefix_len; - let new_len = prev_len + replacement_len - replaced.len(); - let new_range = shifted..shifted + new_len; - let at_end = end == children.len(); - - // Stop parsing early if this kind is encountered. - let stop_kind = match parent_kind { - Some(_) => SyntaxKind::RightBracket, - None => SyntaxKind::Eof, - }; - - // Reparse! - let reparsed = reparse_markup( - text, - new_range.clone(), - &mut at_start, - &mut nesting, - |kind| kind == stop_kind, - ); - - if let Some(newborns) = reparsed { - // If more children follow, at_start must match its previous value. - // Similarly, if we children follow or we not top-level the nesting - // must match its previous value. - if (at_end || at_start == prev_at_start_after) - && ((at_end && parent_kind.is_none()) || nesting == prev_nesting_after) - { - return node - .replace_children(start..end, newborns) - .is_ok() - .then_some(new_range); - } - } - - // If it didn't even work with all children, we give up. - if start == 0 && at_end { - break; - } - - // Exponential expansion to both sides. - expansion *= 2; - } - - None -} - -/// Whether the inner range is fully contained in the outer one (no touching). -fn includes(outer: &Range<usize>, inner: &Range<usize>) -> bool { - outer.start < inner.start && outer.end > inner.end -} - -/// Whether the first and second range overlap or touch. -fn overlaps(first: &Range<usize>, second: &Range<usize>) -> bool { - (first.start <= second.start && second.start <= first.end) - || (second.start <= first.start && first.start <= second.end) -} - -/// Whether the selection should be expanded beyond a node of this kind. -fn expand(node: &SyntaxNode) -> bool { - let kind = node.kind(); - kind.is_trivia() - || kind.is_error() - || kind == SyntaxKind::Semicolon - || node.text() == "/" - || node.text() == ":" -} - -/// Whether `at_start` would still be true after this node given the -/// previous value of the property. -fn next_at_start(node: &SyntaxNode, at_start: &mut bool) { - let kind = node.kind(); - if kind.is_trivia() { - *at_start |= kind == SyntaxKind::Parbreak - || (kind == SyntaxKind::Space && node.text().chars().any(is_newline)); - } else { - *at_start = false; - } -} - -/// Update `nesting` based on the node. -fn next_nesting(node: &SyntaxNode, nesting: &mut usize) { - if node.kind() == SyntaxKind::Text { - match node.text().as_str() { - "[" => *nesting += 1, - "]" if *nesting > 0 => *nesting -= 1, - _ => {} - } - } -} - -#[cfg(test)] -mod tests { - use std::ops::Range; - - use super::super::{parse, Source, Span}; - - #[track_caller] - fn test(prev: &str, range: Range<usize>, with: &str, incremental: bool) { - let mut source = Source::detached(prev); - let prev = source.root().clone(); - let range = source.edit(range, with); - let mut found = source.root().clone(); - let mut expected = parse(source.text()); - found.synthesize(Span::detached()); - expected.synthesize(Span::detached()); - if found != expected { - eprintln!("source: {:?}", source.text()); - eprintln!("previous: {prev:#?}"); - eprintln!("expected: {expected:#?}"); - eprintln!("found: {found:#?}"); - panic!("test failed"); - } - if incremental { - assert_ne!(source.len_bytes(), range.len(), "should have been incremental"); - } else { - assert_eq!( - source.len_bytes(), - range.len(), - "shouldn't have been incremental" - ); - } - } - - #[test] - fn test_reparse_markup() { - test("abc~def~gh~", 5..6, "+", true); - test("~~~~~~~", 3..4, "A", true); - test("abc~~", 1..2, "", true); - test("#var. hello", 5..6, " ", false); - test("#var;hello", 9..10, "a", false); - test("https:/world", 7..7, "/", false); - test("hello world", 7..12, "walkers", false); - test("some content", 0..12, "", false); - test("", 0..0, "do it", false); - test("a d e", 1..3, " b c d", false); - test("~*~*~", 2..2, "*", false); - test("::1\n2. a\n3", 7..7, "4", true); - test("* #{1+2} *", 6..7, "3", true); - test("#{(0, 1, 2)}", 6..7, "11pt", true); - test("\n= A heading", 4..4, "n evocative", false); - test("#call() abc~d", 7..7, "[]", true); - test("a your thing a", 6..7, "a", false); - test("#grid(columns: (auto, 1fr, 40%))", 16..20, "4pt", false); - test("abc\n= a heading\njoke", 3..4, "\nmore\n\n", true); - test("#show f: a => b..", 16..16, "c", false); - test("#for", 4..4, "//", false); - test("a\n#let \nb", 7..7, "i", true); - test(r"#{{let x = z}; a = 1} b", 7..7, "//", false); - test(r#"a ```typst hello```"#, 16..17, "", false); - } - - #[test] - fn test_reparse_block() { - test("Hello #{ x + 1 }!", 9..10, "abc", true); - test("A#{}!", 3..3, "\"", false); - test("#{ [= x] }!", 5..5, "=", true); - test("#[[]]", 3..3, "\\", true); - test("#[[ab]]", 4..5, "\\", true); - test("#{}}", 2..2, "{", false); - test("A: #[BC]", 6..6, "{", true); - test("A: #[BC]", 6..6, "#{", true); - test("A: #[BC]", 6..6, "#{}", true); - test("#{\"ab\"}A", 5..5, "c", true); - test("#{\"ab\"}A", 5..6, "c", false); - test("a#[]b", 3..3, "#{", true); - test("a#{call(); abc}b", 8..8, "[]", true); - test("a #while x {\n g(x) \n} b", 12..12, "//", true); - test("a#[]b", 3..3, "[hey]", true); - } -} diff --git a/src/syntax/source.rs b/src/syntax/source.rs deleted file mode 100644 index 2f3e4144..00000000 --- a/src/syntax/source.rs +++ /dev/null @@ -1,421 +0,0 @@ -//! Source file management. - -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::ops::Range; -use std::sync::Arc; - -use comemo::Prehashed; - -use super::ast::Markup; -use super::reparser::reparse; -use super::{is_newline, parse, LinkedNode, Span, SyntaxNode}; -use crate::diag::SourceResult; -use crate::file::FileId; -use crate::util::StrExt; - -/// A source file. -/// -/// All line and column indices start at zero, just like byte indices. Only for -/// user-facing display, you should add 1 to them. -/// -/// Values of this type are cheap to clone and hash. -#[derive(Clone)] -pub struct Source(Arc<Repr>); - -/// The internal representation. -#[derive(Clone)] -struct Repr { - id: FileId, - text: Prehashed<String>, - root: Prehashed<SyntaxNode>, - lines: Vec<Line>, -} - -impl Source { - /// Create a new source file. - #[tracing::instrument(skip_all)] - pub fn new(id: FileId, text: String) -> Self { - let mut root = parse(&text); - root.numberize(id, Span::FULL).unwrap(); - Self(Arc::new(Repr { - id, - lines: lines(&text), - text: Prehashed::new(text), - root: Prehashed::new(root), - })) - } - - /// Create a source file without a real id and path, usually for testing. - pub fn detached(text: impl Into<String>) -> Self { - Self::new(FileId::detached(), text.into()) - } - - /// Create a source file with the same synthetic span for all nodes. - pub fn synthesized(text: String, span: Span) -> Self { - let mut root = parse(&text); - root.synthesize(span); - Self(Arc::new(Repr { - id: FileId::detached(), - lines: lines(&text), - text: Prehashed::new(text), - root: Prehashed::new(root), - })) - } - - /// The root node of the file's untyped syntax tree. - pub fn root(&self) -> &SyntaxNode { - &self.0.root - } - - /// The root node of the file's typed abstract syntax tree. - pub fn ast(&self) -> SourceResult<Markup> { - let errors = self.root().errors(); - if errors.is_empty() { - Ok(self.root().cast().expect("root node must be markup")) - } else { - Err(Box::new(errors)) - } - } - - /// The id of the source file. - pub fn id(&self) -> FileId { - self.0.id - } - - /// The whole source as a string slice. - pub fn text(&self) -> &str { - &self.0.text - } - - /// Slice out the part of the source code enclosed by the range. - pub fn get(&self, range: Range<usize>) -> Option<&str> { - self.text().get(range) - } - - /// Fully replace the source text. - pub fn replace(&mut self, text: String) { - let inner = Arc::make_mut(&mut self.0); - inner.text = Prehashed::new(text); - inner.lines = lines(&inner.text); - let mut root = parse(&inner.text); - root.numberize(inner.id, Span::FULL).unwrap(); - inner.root = Prehashed::new(root); - } - - /// Edit the source file by replacing the given range. - /// - /// Returns the range in the new source that was ultimately reparsed. - /// - /// The method panics if the `replace` range is out of bounds. - #[track_caller] - pub fn edit(&mut self, replace: Range<usize>, with: &str) -> Range<usize> { - let start_byte = replace.start; - let start_utf16 = self.byte_to_utf16(start_byte).unwrap(); - let line = self.byte_to_line(start_byte).unwrap(); - - let inner = Arc::make_mut(&mut self.0); - - // Update the text itself. - inner.text.update(|text| text.replace_range(replace.clone(), with)); - - // Remove invalidated line starts. - inner.lines.truncate(line + 1); - - // Handle adjoining of \r and \n. - if inner.text[..start_byte].ends_with('\r') && with.starts_with('\n') { - inner.lines.pop(); - } - - // Recalculate the line starts after the edit. - inner.lines.extend(lines_from( - start_byte, - start_utf16, - &inner.text[start_byte..], - )); - - // Incrementally reparse the replaced range. - inner - .root - .update(|root| reparse(root, &inner.text, replace, with.len())) - } - - /// Get the length of the file in UTF-8 encoded bytes. - pub fn len_bytes(&self) -> usize { - self.text().len() - } - - /// Get the length of the file in UTF-16 code units. - pub fn len_utf16(&self) -> usize { - let last = self.0.lines.last().unwrap(); - last.utf16_idx + self.0.text[last.byte_idx..].len_utf16() - } - - /// Get the length of the file in lines. - pub fn len_lines(&self) -> usize { - self.0.lines.len() - } - - /// Find the node with the given span. - /// - /// Returns `None` if the span does not point into this source file. - pub fn find(&self, span: Span) -> Option<LinkedNode<'_>> { - LinkedNode::new(self.root()).find(span) - } - - /// Return the index of the UTF-16 code unit at the byte index. - pub fn byte_to_utf16(&self, byte_idx: usize) -> Option<usize> { - let line_idx = self.byte_to_line(byte_idx)?; - let line = self.0.lines.get(line_idx)?; - let head = self.0.text.get(line.byte_idx..byte_idx)?; - Some(line.utf16_idx + head.len_utf16()) - } - - /// Return the index of the line that contains the given byte index. - pub fn byte_to_line(&self, byte_idx: usize) -> Option<usize> { - (byte_idx <= self.0.text.len()).then(|| { - match self.0.lines.binary_search_by_key(&byte_idx, |line| line.byte_idx) { - Ok(i) => i, - Err(i) => i - 1, - } - }) - } - - /// Return the index of the column at the byte index. - /// - /// The column is defined as the number of characters in the line before the - /// byte index. - pub fn byte_to_column(&self, byte_idx: usize) -> Option<usize> { - let line = self.byte_to_line(byte_idx)?; - let start = self.line_to_byte(line)?; - let head = self.get(start..byte_idx)?; - Some(head.chars().count()) - } - - /// Return the byte index at the UTF-16 code unit. - pub fn utf16_to_byte(&self, utf16_idx: usize) -> Option<usize> { - let line = self.0.lines.get( - match self.0.lines.binary_search_by_key(&utf16_idx, |line| line.utf16_idx) { - Ok(i) => i, - Err(i) => i - 1, - }, - )?; - - let mut k = line.utf16_idx; - for (i, c) in self.0.text[line.byte_idx..].char_indices() { - if k >= utf16_idx { - return Some(line.byte_idx + i); - } - k += c.len_utf16(); - } - - (k == utf16_idx).then_some(self.0.text.len()) - } - - /// Return the byte position at which the given line starts. - pub fn line_to_byte(&self, line_idx: usize) -> Option<usize> { - self.0.lines.get(line_idx).map(|line| line.byte_idx) - } - - /// Return the range which encloses the given line. - pub fn line_to_range(&self, line_idx: usize) -> Option<Range<usize>> { - let start = self.line_to_byte(line_idx)?; - let end = self.line_to_byte(line_idx + 1).unwrap_or(self.0.text.len()); - Some(start..end) - } - - /// Return the byte index of the given (line, column) pair. - /// - /// The column defines the number of characters to go beyond the start of - /// the line. - pub fn line_column_to_byte( - &self, - line_idx: usize, - column_idx: usize, - ) -> Option<usize> { - let range = self.line_to_range(line_idx)?; - let line = self.get(range.clone())?; - let mut chars = line.chars(); - for _ in 0..column_idx { - chars.next(); - } - Some(range.start + (line.len() - chars.as_str().len())) - } -} - -impl Debug for Source { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Source({})", self.id().path().display()) - } -} - -impl Hash for Source { - fn hash<H: Hasher>(&self, state: &mut H) { - self.0.id.hash(state); - self.0.text.hash(state); - self.0.root.hash(state); - } -} - -impl AsRef<str> for Source { - fn as_ref(&self) -> &str { - self.text() - } -} - -/// Metadata about a line. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -struct Line { - /// The UTF-8 byte offset where the line starts. - byte_idx: usize, - /// The UTF-16 codepoint offset where the line starts. - utf16_idx: usize, -} - -/// Create a line vector. -fn lines(text: &str) -> Vec<Line> { - std::iter::once(Line { byte_idx: 0, utf16_idx: 0 }) - .chain(lines_from(0, 0, text)) - .collect() -} - -/// Compute a line iterator from an offset. -fn lines_from( - byte_offset: usize, - utf16_offset: usize, - text: &str, -) -> impl Iterator<Item = Line> + '_ { - let mut s = unscanny::Scanner::new(text); - let mut utf16_idx = utf16_offset; - - std::iter::from_fn(move || { - s.eat_until(|c: char| { - utf16_idx += c.len_utf16(); - is_newline(c) - }); - - if s.done() { - return None; - } - - if s.eat() == Some('\r') && s.eat_if('\n') { - utf16_idx += 1; - } - - Some(Line { byte_idx: byte_offset + s.cursor(), utf16_idx }) - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - const TEST: &str = "ä\tcde\nf💛g\r\nhi\rjkl"; - - #[test] - fn test_source_file_new() { - let source = Source::detached(TEST); - assert_eq!( - source.0.lines, - [ - Line { byte_idx: 0, utf16_idx: 0 }, - Line { byte_idx: 7, utf16_idx: 6 }, - Line { byte_idx: 15, utf16_idx: 12 }, - Line { byte_idx: 18, utf16_idx: 15 }, - ] - ); - } - - #[test] - fn test_source_file_pos_to_line() { - let source = Source::detached(TEST); - assert_eq!(source.byte_to_line(0), Some(0)); - assert_eq!(source.byte_to_line(2), Some(0)); - assert_eq!(source.byte_to_line(6), Some(0)); - assert_eq!(source.byte_to_line(7), Some(1)); - assert_eq!(source.byte_to_line(8), Some(1)); - assert_eq!(source.byte_to_line(12), Some(1)); - assert_eq!(source.byte_to_line(21), Some(3)); - assert_eq!(source.byte_to_line(22), None); - } - - #[test] - fn test_source_file_pos_to_column() { - let source = Source::detached(TEST); - assert_eq!(source.byte_to_column(0), Some(0)); - assert_eq!(source.byte_to_column(2), Some(1)); - assert_eq!(source.byte_to_column(6), Some(5)); - assert_eq!(source.byte_to_column(7), Some(0)); - assert_eq!(source.byte_to_column(8), Some(1)); - assert_eq!(source.byte_to_column(12), Some(2)); - } - - #[test] - fn test_source_file_utf16() { - #[track_caller] - fn roundtrip(source: &Source, byte_idx: usize, utf16_idx: usize) { - let middle = source.byte_to_utf16(byte_idx).unwrap(); - let result = source.utf16_to_byte(middle).unwrap(); - assert_eq!(middle, utf16_idx); - assert_eq!(result, byte_idx); - } - - let source = Source::detached(TEST); - roundtrip(&source, 0, 0); - roundtrip(&source, 2, 1); - roundtrip(&source, 3, 2); - roundtrip(&source, 8, 7); - roundtrip(&source, 12, 9); - roundtrip(&source, 21, 18); - assert_eq!(source.byte_to_utf16(22), None); - assert_eq!(source.utf16_to_byte(19), None); - } - - #[test] - fn test_source_file_roundtrip() { - #[track_caller] - fn roundtrip(source: &Source, byte_idx: usize) { - let line = source.byte_to_line(byte_idx).unwrap(); - let column = source.byte_to_column(byte_idx).unwrap(); - let result = source.line_column_to_byte(line, column).unwrap(); - assert_eq!(result, byte_idx); - } - - let source = Source::detached(TEST); - roundtrip(&source, 0); - roundtrip(&source, 7); - roundtrip(&source, 12); - roundtrip(&source, 21); - } - - #[test] - fn test_source_file_edit() { - // This tests only the non-parser parts. The reparsing itself is - // tested separately. - #[track_caller] - fn test(prev: &str, range: Range<usize>, with: &str, after: &str) { - let mut source = Source::detached(prev); - let result = Source::detached(after); - source.edit(range, with); - assert_eq!(source.text(), result.text()); - assert_eq!(source.0.lines, result.0.lines); - } - - // Test inserting at the beginning. - test("abc\n", 0..0, "hi\n", "hi\nabc\n"); - test("\nabc", 0..0, "hi\r", "hi\r\nabc"); - - // Test editing in the middle. - test(TEST, 4..16, "❌", "ä\tc❌i\rjkl"); - - // Test appending. - test("abc\ndef", 7..7, "hi", "abc\ndefhi"); - test("abc\ndef\n", 8..8, "hi", "abc\ndef\nhi"); - - // Test appending with adjoining \r and \n. - test("abc\ndef\r", 8..8, "\nghi", "abc\ndef\r\nghi"); - - // Test removing everything. - test(TEST, 0..21, "", ""); - } -} diff --git a/src/syntax/span.rs b/src/syntax/span.rs deleted file mode 100644 index 5c220252..00000000 --- a/src/syntax/span.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::num::NonZeroU64; -use std::ops::Range; - -use super::Source; -use crate::file::FileId; -use crate::World; - -/// A unique identifier for a syntax node. -/// -/// This is used throughout the compiler to track which source section an error -/// or element stems from. Can be [mapped back](Self::range) to a byte range for -/// user facing display. -/// -/// During editing, the span values stay mostly stable, even for nodes behind an -/// insertion. This is not true for simple ranges as they would shift. Spans can -/// be used as inputs to memoized functions without hurting cache performance -/// when text is inserted somewhere in the document other than the end. -/// -/// Span ids are ordered in the syntax tree to enable quickly finding the node -/// with some id: -/// - The id of a parent is always smaller than the ids of any of its children. -/// - The id of a node is always greater than any id in the subtrees of any left -/// sibling and smaller than any id in the subtrees of any right sibling. -/// -/// This type takes up 8 bytes and is null-optimized (i.e. `Option<Span>` also -/// takes 8 bytes). -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Span(NonZeroU64); - -impl Span { - /// The full range of numbers available for span numbering. - pub const FULL: Range<u64> = 2..(1 << Self::BITS); - const DETACHED: u64 = 1; - - // Data layout: - // | 16 bits source id | 48 bits number | - const BITS: usize = 48; - - /// Create a new span from a source id and a unique number. - /// - /// Panics if the `number` is not contained in `FULL`. - #[track_caller] - pub const fn new(id: FileId, number: u64) -> Self { - assert!( - Self::FULL.start <= number && number < Self::FULL.end, - "span number outside valid range" - ); - - Self::pack(id, number) - } - - /// A span that does not point into any source file. - pub const fn detached() -> Self { - Self::pack(FileId::detached(), Self::DETACHED) - } - - /// Pack the components into a span. - #[track_caller] - const fn pack(id: FileId, number: u64) -> Span { - let bits = ((id.as_u16() as u64) << Self::BITS) | number; - match NonZeroU64::new(bits) { - Some(v) => Self(v), - None => panic!("span encoding is zero"), - } - } - - /// The id of the source file the span points into. - pub const fn id(self) -> FileId { - FileId::from_u16((self.0.get() >> Self::BITS) as u16) - } - - /// The unique number of the span within its source file. - pub const fn number(self) -> u64 { - self.0.get() & ((1 << Self::BITS) - 1) - } - - /// Whether the span is detached. - pub const fn is_detached(self) -> bool { - self.id().is_detached() - } - - /// Get the byte range for this span. - #[track_caller] - pub fn range(self, world: &dyn World) -> Range<usize> { - let source = world - .source(self.id()) - .expect("span does not point into any source file"); - self.range_in(&source) - } - - /// Get the byte range for this span in the given source file. - #[track_caller] - pub fn range_in(self, source: &Source) -> Range<usize> { - source - .find(self) - .expect("span does not point into this source file") - .range() - } -} - -/// A value with a span locating it in the source code. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Spanned<T> { - /// The spanned value. - pub v: T, - /// The value's location in source code. - pub span: Span, -} - -impl<T> Spanned<T> { - /// Create a new instance from a value and its span. - pub fn new(v: T, span: Span) -> Self { - Self { v, span } - } - - /// Convert from `&Spanned<T>` to `Spanned<&T>` - pub fn as_ref(&self) -> Spanned<&T> { - Spanned { v: &self.v, span: self.span } - } - - /// Map the value using a function. - pub fn map<F, U>(self, f: F) -> Spanned<U> - where - F: FnOnce(T) -> U, - { - Spanned { v: f(self.v), span: self.span } - } -} - -impl<T: Debug> Debug for Spanned<T> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.v.fmt(f) - } -} - -#[cfg(test)] -mod tests { - use super::{FileId, Span}; - - #[test] - fn test_span_encoding() { - let id = FileId::from_u16(5); - let span = Span::new(id, 10); - assert_eq!(span.id(), id); - assert_eq!(span.number(), 10); - } -} diff --git a/src/util/bytes.rs b/src/util/bytes.rs deleted file mode 100644 index 9165467b..00000000 --- a/src/util/bytes.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::borrow::Cow; -use std::fmt::{self, Debug, Formatter}; -use std::ops::Deref; -use std::sync::Arc; - -use comemo::Prehashed; - -/// A shared byte buffer that is cheap to clone and hash. -#[derive(Clone, Hash, Eq, PartialEq)] -pub struct Bytes(Arc<Prehashed<Cow<'static, [u8]>>>); - -impl Bytes { - /// Create a buffer from a static byte slice. - pub fn from_static(slice: &'static [u8]) -> Self { - Self(Arc::new(Prehashed::new(Cow::Borrowed(slice)))) - } - - /// Return a view into the buffer. - pub fn as_slice(&self) -> &[u8] { - self - } - - /// Return a copy of the buffer as a vector. - pub fn to_vec(&self) -> Vec<u8> { - self.0.to_vec() - } -} - -impl From<&[u8]> for Bytes { - fn from(slice: &[u8]) -> Self { - Self(Arc::new(Prehashed::new(slice.to_vec().into()))) - } -} - -impl From<Vec<u8>> for Bytes { - fn from(vec: Vec<u8>) -> Self { - Self(Arc::new(Prehashed::new(vec.into()))) - } -} - -impl Deref for Bytes { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef<[u8]> for Bytes { - fn as_ref(&self) -> &[u8] { - self - } -} - -impl Debug for Bytes { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "bytes({})", self.len()) - } -} diff --git a/src/util/fat.rs b/src/util/fat.rs deleted file mode 100644 index d3c9bb20..00000000 --- a/src/util/fat.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Fat pointer handling. -//! -//! This assumes the memory representation of fat pointers. Although it is not -//! guaranteed by Rust, it's improbable that it will change. Still, when the -//! pointer metadata APIs are stable, we should definitely move to them: -//! <https://github.com/rust-lang/rust/issues/81513> - -use std::alloc::Layout; -use std::mem; - -/// Create a fat pointer from a data address and a vtable address. -/// -/// # Safety -/// Must only be called when `T` is a `dyn Trait`. The data address must point -/// to a value whose type implements the trait of `T` and the `vtable` must have -/// been extracted with [`vtable`]. -#[track_caller] -pub unsafe fn from_raw_parts<T: ?Sized>(data: *const (), vtable: *const ()) -> *const T { - let fat = FatPointer { data, vtable }; - debug_assert_eq!(Layout::new::<*const T>(), Layout::new::<FatPointer>()); - mem::transmute_copy::<FatPointer, *const T>(&fat) -} - -/// Create a mutable fat pointer from a data address and a vtable address. -/// -/// # Safety -/// Must only be called when `T` is a `dyn Trait`. The data address must point -/// to a value whose type implements the trait of `T` and the `vtable` must have -/// been extracted with [`vtable`]. -#[track_caller] -pub unsafe fn from_raw_parts_mut<T: ?Sized>(data: *mut (), vtable: *const ()) -> *mut T { - let fat = FatPointer { data, vtable }; - debug_assert_eq!(Layout::new::<*mut T>(), Layout::new::<FatPointer>()); - mem::transmute_copy::<FatPointer, *mut T>(&fat) -} - -/// Extract the address to a trait object's vtable. -/// -/// # Safety -/// Must only be called when `T` is a `dyn Trait`. -#[track_caller] -pub unsafe fn vtable<T: ?Sized>(ptr: *const T) -> *const () { - debug_assert_eq!(Layout::new::<*const T>(), Layout::new::<FatPointer>()); - mem::transmute_copy::<*const T, FatPointer>(&ptr).vtable -} - -/// The memory representation of a trait object pointer. -/// -/// Although this is not guaranteed by Rust, it's improbable that it will -/// change. -#[repr(C)] -struct FatPointer { - data: *const (), - vtable: *const (), -} diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index 05914b04..00000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! Utilities. - -pub mod fat; - -mod bytes; - -pub use bytes::Bytes; - -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::num::NonZeroUsize; -use std::path::{Component, Path, PathBuf}; -use std::sync::Arc; - -use siphasher::sip128::{Hasher128, SipHasher13}; - -/// Turn a closure into a struct implementing [`Debug`]. -pub fn debug<F>(f: F) -> impl Debug -where - F: Fn(&mut Formatter) -> fmt::Result, -{ - struct Wrapper<F>(F); - - impl<F> Debug for Wrapper<F> - where - F: Fn(&mut Formatter) -> fmt::Result, - { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.0(f) - } - } - - Wrapper(f) -} - -/// Calculate a 128-bit siphash of a value. -pub fn hash128<T: Hash + ?Sized>(value: &T) -> u128 { - let mut state = SipHasher13::new(); - value.hash(&mut state); - state.finish128().as_u128() -} - -/// An extra constant 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 - /// UTF16. This runs in linear time. - fn len_utf16(&self) -> usize; -} - -impl StrExt for str { - fn len_utf16(&self) -> usize { - self.chars().map(char::len_utf16).sum() - } -} - -/// Extra methods for [`Arc`]. -pub trait ArcExt<T> { - /// Takes the inner value if there is exactly one strong reference and - /// clones it otherwise. - fn take(self) -> T; -} - -impl<T: Clone> ArcExt<T> for Arc<T> { - fn take(self) -> T { - match Arc::try_unwrap(self) { - Ok(v) => v, - Err(rc) => (*rc).clone(), - } - } -} - -/// Extra methods for [`[T]`](slice). -pub trait SliceExt<T> { - /// Split a slice into consecutive runs with the same key and yield for - /// each such run the key and the slice of elements with that key. - fn group_by_key<K, F>(&self, f: F) -> GroupByKey<'_, T, F> - where - F: FnMut(&T) -> K, - K: PartialEq; -} - -impl<T> SliceExt<T> for [T] { - fn group_by_key<K, F>(&self, f: F) -> GroupByKey<'_, T, F> { - GroupByKey { slice: self, f } - } -} - -/// This struct is created by [`SliceExt::group_by_key`]. -pub struct GroupByKey<'a, T, F> { - slice: &'a [T], - f: F, -} - -impl<'a, T, K, F> Iterator for GroupByKey<'a, T, F> -where - F: FnMut(&T) -> K, - K: PartialEq, -{ - type Item = (K, &'a [T]); - - fn next(&mut self) -> Option<Self::Item> { - let mut iter = self.slice.iter(); - let key = (self.f)(iter.next()?); - let count = 1 + iter.take_while(|t| (self.f)(t) == key).count(); - let (head, tail) = self.slice.split_at(count); - self.slice = tail; - Some((key, head)) - } -} - -/// Extra methods for [`Path`]. -pub trait PathExt { - /// Lexically normalize a path. - fn normalize(&self) -> PathBuf; - - /// Treat `self` as a virtual root relative to which the `path` is resolved. - /// - /// Returns `None` if the path lexically escapes the root. The path - /// might still escape through symlinks. - fn join_rooted(&self, path: &Path) -> Option<PathBuf>; -} - -impl PathExt for Path { - fn normalize(&self) -> PathBuf { - let mut out = PathBuf::new(); - for component in self.components() { - match component { - Component::CurDir => {} - Component::ParentDir => match out.components().next_back() { - Some(Component::Normal(_)) => { - out.pop(); - } - _ => out.push(component), - }, - Component::Prefix(_) | Component::RootDir | Component::Normal(_) => { - out.push(component) - } - } - } - if out.as_os_str().is_empty() { - out.push(Component::CurDir); - } - out - } - - fn join_rooted(&self, path: &Path) -> Option<PathBuf> { - let mut parts: Vec<_> = self.components().collect(); - let root = parts.len(); - for component in path.components() { - match component { - Component::Prefix(_) => return None, - Component::RootDir => parts.truncate(root), - Component::CurDir => {} - Component::ParentDir => { - if parts.len() <= root { - return None; - } - parts.pop(); - } - Component::Normal(_) => parts.push(component), - } - } - if parts.len() < root { - return None; - } - Some(parts.into_iter().collect()) - } -} - -/// Format pieces separated with commas and a final "and" or "or". -pub fn separated_list(pieces: &[impl AsRef<str>], last: &str) -> String { - let mut buf = String::new(); - for (i, part) in pieces.iter().enumerate() { - match i { - 0 => {} - 1 if pieces.len() == 2 => { - buf.push(' '); - buf.push_str(last); - buf.push(' '); - } - i if i + 1 == pieces.len() => { - buf.push_str(", "); - buf.push_str(last); - buf.push(' '); - } - _ => buf.push_str(", "), - } - buf.push_str(part.as_ref()); - } - buf -} - -/// Format a comma-separated list. -/// -/// Tries to format horizontally, but falls back to vertical formatting if the -/// pieces are too long. -pub fn pretty_comma_list(pieces: &[impl AsRef<str>], trailing_comma: bool) -> String { - const MAX_WIDTH: usize = 50; - - let mut buf = String::new(); - let len = pieces.iter().map(|s| s.as_ref().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.as_ref()); - } - if trailing_comma { - buf.push(','); - } - } else { - for piece in pieces { - buf.push_str(piece.as_ref().trim()); - buf.push_str(",\n"); - } - } - - buf -} - -/// Format an array-like construct. -/// -/// Tries to format horizontally, but falls back to vertical formatting if the -/// pieces are too long. -pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> String { - let list = pretty_comma_list(parts, trailing_comma); - let mut buf = String::new(); - buf.push('('); - if list.contains('\n') { - buf.push('\n'); - for (i, line) in list.lines().enumerate() { - if i > 0 { - buf.push('\n'); - } - buf.push_str(" "); - buf.push_str(line); - } - buf.push('\n'); - } else { - buf.push_str(&list); - } - buf.push(')'); - buf -} - -/// Check if the [`Option`]-wrapped L is same to R. -pub fn option_eq<L, R>(left: Option<L>, other: R) -> bool -where - L: PartialEq<R>, -{ - left.map(|v| v == other).unwrap_or(false) -} |
