diff --git a/Cargo.lock b/Cargo.lock index b24ffec12..7306cdd1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,6 +304,11 @@ dependencies = [ "winapi", ] +[[package]] +name = "ecow" +version = "0.1.0" +source = "git+https://github.com/typst/ecow#36b624de42f12f90ebad3e0498d770e1700f2e90" + [[package]] name = "elsa" version = "1.7.0" @@ -1166,6 +1171,7 @@ dependencies = [ "bitflags", "bytemuck", "comemo", + "ecow", "flate2", "if_chain", "image", @@ -1238,6 +1244,7 @@ version = "0.0.0" dependencies = [ "comemo", "csv", + "ecow", "hypher", "kurbo", "lipsum", @@ -1569,7 +1576,8 @@ checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" [[package]] name = "xmp-writer" version = "0.1.0" -source = "git+https://github.com/typst/xmp-writer#0bce4e38395877dad229ea4518d4f78038738155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd742bbbb930fc972b28bf66b7546dfbc7bb9a4c7924299df0ae6a5641fcadf" [[package]] name = "yaml-front-matter" diff --git a/Cargo.toml b/Cargo.toml index 8bd8e67e7..bf5b772b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ typst-macros = { path = "macros" } bitflags = "1" bytemuck = "1" comemo = { git = "https://github.com/typst/comemo" } +ecow = { git = "https://github.com/typst/ecow" } flate2 = "1" if_chain = "1" image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] } @@ -40,7 +41,7 @@ unicode-segmentation = "1" unicode-xid = "0.2" unscanny = "0.1" usvg = { version = "0.22", default-features = false } -xmp-writer = { git = "https://github.com/typst/xmp-writer" } +xmp-writer = "0.1" [profile.dev] debug = 0 diff --git a/library/Cargo.toml b/library/Cargo.toml index af08d73b6..d725a3936 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -13,6 +13,7 @@ bench = false typst = { path = ".." } comemo = { git = "https://github.com/typst/comemo" } csv = "1" +ecow = { git = "https://github.com/typst/ecow" } hypher = "0.1" kurbo = "0.8" lipsum = { git = "https://github.com/reknih/lipsum" } diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs index f540d8288..a44d50c2a 100644 --- a/library/src/compute/construct.rs +++ b/library/src/compute/construct.rs @@ -458,12 +458,12 @@ pub fn range(args: &mut Args) -> SourceResult { }; let mut x = start; - let mut seq = vec![]; + let mut array = Array::new(); while x.cmp(&end) == 0.cmp(&step) { - seq.push(Value::Int(x)); + array.push(Value::Int(x)); x += step; } - Ok(Value::Array(Array::from_vec(seq))) + Ok(Value::Array(array)) } diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index 5c0d4e64c..c604be118 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -86,15 +86,15 @@ pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult { } let mut reader = builder.from_reader(data.as_slice()); - let mut vec = vec![]; + let mut array = Array::new(); for result in reader.records() { let row = result.map_err(format_csv_error).at(span)?; - let array = row.iter().map(|field| Value::Str(field.into())).collect(); - vec.push(Value::Array(array)) + let sub = row.iter().map(|field| Value::Str(field.into())).collect(); + array.push(Value::Array(sub)) } - Ok(Value::Array(Array::from_vec(vec))) + Ok(Value::Array(array)) } /// The delimiter to use when parsing CSV files. diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 5bb1d08a9..c8f6fe97c 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -8,6 +8,8 @@ pub use std::num::NonZeroUsize; #[doc(no_inline)] pub use comemo::{Track, Tracked, TrackedMut}; #[doc(no_inline)] +pub use ecow::{format_eco, EcoString}; +#[doc(no_inline)] pub use typst::diag::{bail, error, At, SourceResult, StrResult}; #[doc(no_inline)] pub use typst::doc::*; @@ -23,8 +25,6 @@ pub use typst::model::{ #[doc(no_inline)] pub use typst::syntax::{Span, Spanned}; #[doc(no_inline)] -pub use typst::util::{format_eco, EcoString}; -#[doc(no_inline)] pub use typst::World; #[doc(no_inline)] diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index a87fba872..1ef32fa44 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -18,7 +18,6 @@ use std::borrow::Cow; use rustybuzz::Tag; use typst::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; -use typst::util::EcoString; use crate::layout::ParNode; use crate::prelude::*; diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index a79676500..d68095918 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -1,5 +1,4 @@ use typst::model::SequenceNode; -use typst::util::EcoString; use super::{variant, SpaceNode, TextNode, TextSize}; use crate::prelude::*; diff --git a/src/diag.rs b/src/diag.rs index 1cf2f85b5..2742a8724 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -8,9 +8,9 @@ use std::str::Utf8Error; use std::string::FromUtf8Error; use comemo::Tracked; +use ecow::EcoString; use crate::syntax::{ErrorPos, Span, Spanned}; -use crate::util::{format_eco, EcoString}; use crate::World; /// Early-return with a [`SourceError`]. @@ -38,12 +38,14 @@ macro_rules! __error { }; ($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => { - $crate::diag::error!($span, $crate::util::format_eco!($fmt, $($arg),+)) + $crate::diag::error!($span, $crate::diag::format_eco!($fmt, $($arg),+)) }; } #[doc(inline)] pub use crate::__error as error; +#[doc(hidden)] +pub use ecow::format_eco; /// A result that can carry multiple source errors. pub type SourceResult = Result>>; diff --git a/src/doc.rs b/src/doc.rs index 0ca930bf8..f4a4d9686 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -5,6 +5,8 @@ use std::num::NonZeroUsize; use std::str::FromStr; use std::sync::Arc; +use ecow::EcoString; + use crate::font::Font; use crate::geom::{ self, rounded_rect, Abs, Align, Axes, Color, Corners, Dir, Em, Geometry, Numeric, @@ -14,7 +16,6 @@ use crate::image::Image; use crate::model::{ capable, dict, node, Content, Dict, Fold, StableId, StyleChain, Value, }; -use crate::util::EcoString; /// A finished document with metadata and page frames. #[derive(Debug, Default, Clone, Hash)] diff --git a/src/export/pdf/font.rs b/src/export/pdf/font.rs index 075e34908..533911056 100644 --- a/src/export/pdf/font.rs +++ b/src/export/pdf/font.rs @@ -1,11 +1,12 @@ use std::collections::BTreeMap; +use ecow::format_eco; use pdf_writer::types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap}; use pdf_writer::{Filter, Finish, Name, Rect, Str}; use ttf_parser::{name_id, GlyphId, Tag}; use super::{deflate, EmExt, PdfContext, RefExt}; -use crate::util::{format_eco, SliceExt}; +use crate::util::SliceExt; /// Embed all used fonts into the PDF. pub fn write_fonts(ctx: &mut PdfContext) { diff --git a/src/export/pdf/outline.rs b/src/export/pdf/outline.rs index e7a356c13..1b335474b 100644 --- a/src/export/pdf/outline.rs +++ b/src/export/pdf/outline.rs @@ -1,8 +1,8 @@ +use ecow::EcoString; use pdf_writer::{Finish, Ref, TextStr}; use super::{AbsExt, PdfContext, RefExt}; use crate::geom::{Abs, Point}; -use crate::util::EcoString; /// A heading in the outline panel. #[derive(Debug, Clone)] diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index 2ae93fc73..7daf9d5ed 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -1,3 +1,4 @@ +use ecow::format_eco; use pdf_writer::types::{ActionType, AnnotationType, ColorSpaceOperand}; use pdf_writer::writers::ColorSpace; use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str}; @@ -10,7 +11,6 @@ use crate::geom::{ Transform, }; use crate::image::Image; -use crate::util::format_eco; /// Construct page objects. pub fn construct_pages(ctx: &mut PdfContext, frames: &[Frame]) { diff --git a/src/ide/complete.rs b/src/ide/complete.rs index 8db1bf82f..607f2558d 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -1,11 +1,11 @@ use std::collections::{BTreeSet, HashSet}; +use ecow::{format_eco, EcoString}; use if_chain::if_chain; use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family}; use crate::model::{methods_on, CastInfo, Scope, Value}; use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; -use crate::util::{format_eco, EcoString}; use crate::World; /// Autocomplete a cursor position in a source file. diff --git a/src/model/array.rs b/src/model/array.rs index 12abac4ee..0a84072d1 100644 --- a/src/model/array.rs +++ b/src/model/array.rs @@ -1,31 +1,33 @@ use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; -use std::sync::Arc; + +use ecow::{format_eco, EcoString, EcoVec}; use super::{ops, Args, Func, Value, Vm}; use crate::diag::{bail, At, SourceResult, StrResult}; -use crate::util::{format_eco, ArcExt, EcoString}; /// Create a new [`Array`] from values. #[macro_export] #[doc(hidden)] macro_rules! __array { ($value:expr; $count:expr) => { - $crate::model::Array::from_vec(vec![$value.into(); $count]) + $crate::model::Array::from_vec($crate::model::eco_vec![$value.into(); $count]) }; ($($value:expr),* $(,)?) => { - $crate::model::Array::from_vec(vec![$($value.into()),*]) + $crate::model::Array::from_vec($crate::model::eco_vec![$($value.into()),*]) }; } #[doc(inline)] pub use crate::__array as array; +#[doc(hidden)] +pub use ecow::eco_vec; /// A reference counted array with value semantics. #[derive(Default, Clone, PartialEq, Hash)] -pub struct Array(Arc>); +pub struct Array(EcoVec); impl Array { /// Create a new, empty array. @@ -33,9 +35,9 @@ impl Array { Self::default() } - /// Create a new array from a vector of values. - pub fn from_vec(vec: Vec) -> Self { - Self(Arc::new(vec)) + /// Create a new array from an eco vector of values. + pub fn from_vec(vec: EcoVec) -> Self { + Self(vec) } /// The length of the array. @@ -50,7 +52,7 @@ impl Array { /// Mutably borrow the first value in the array. pub fn first_mut(&mut self) -> StrResult<&mut Value> { - Arc::make_mut(&mut self.0).first_mut().ok_or_else(array_is_empty) + self.0.make_mut().first_mut().ok_or_else(array_is_empty) } /// The last value in the array. @@ -60,7 +62,7 @@ impl Array { /// Mutably borrow the last value in the array. pub fn last_mut(&mut self) -> StrResult<&mut Value> { - Arc::make_mut(&mut self.0).last_mut().ok_or_else(array_is_empty) + self.0.make_mut().last_mut().ok_or_else(array_is_empty) } /// Borrow the value at the given index. @@ -74,18 +76,18 @@ impl Array { pub fn at_mut(&mut self, index: i64) -> StrResult<&mut Value> { let len = self.len(); self.locate(index) - .and_then(move |i| Arc::make_mut(&mut self.0).get_mut(i)) + .and_then(move |i| self.0.make_mut().get_mut(i)) .ok_or_else(|| out_of_bounds(index, len)) } /// Push a value to the end of the array. pub fn push(&mut self, value: Value) { - Arc::make_mut(&mut self.0).push(value); + self.0.push(value); } /// Remove the last value in the array. pub fn pop(&mut self) -> StrResult { - Arc::make_mut(&mut self.0).pop().ok_or_else(array_is_empty) + self.0.pop().ok_or_else(array_is_empty) } /// Insert a value at the specified index. @@ -96,7 +98,7 @@ impl Array { .filter(|&i| i <= self.0.len()) .ok_or_else(|| out_of_bounds(index, len))?; - Arc::make_mut(&mut self.0).insert(i, value); + self.0.insert(i, value); Ok(()) } @@ -108,7 +110,7 @@ impl Array { .filter(|&i| i < self.0.len()) .ok_or_else(|| out_of_bounds(index, len))?; - Ok(Arc::make_mut(&mut self.0).remove(i)) + Ok(self.0.remove(i)) } /// Extract a contigous subregion of the array. @@ -126,7 +128,7 @@ impl Array { .ok_or_else(|| out_of_bounds(end, len))? .max(start); - Ok(Self::from_vec(self.0[start..end].to_vec())) + Ok(Self::from_vec(self.0[start..end].into())) } /// Whether the array contains a specific value. @@ -170,7 +172,7 @@ impl Array { if func.argc().map_or(false, |count| count != 1) { bail!(func.span(), "function must have exactly one parameter"); } - let mut kept = vec![]; + let mut kept = EcoVec::new(); for item in self.iter() { let args = Args::new(func.span(), [item.clone()]); if func.call(vm, args)?.cast::().at(func.span())? { @@ -244,7 +246,7 @@ impl Array { /// Return a new array with all items from this and nested arrays. pub fn flatten(&self) -> Self { - let mut flat = Vec::with_capacity(self.0.len()); + 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()); @@ -287,8 +289,8 @@ impl Array { /// Returns an error if two values could not be compared. pub fn sorted(&self) -> StrResult { let mut result = Ok(()); - let mut vec = (*self.0).clone(); - vec.sort_by(|a, b| { + let mut vec = self.0.clone(); + vec.make_mut().sort_by(|a, b| { a.partial_cmp(b).unwrap_or_else(|| { if result.is_ok() { result = Err(format_eco!( @@ -369,31 +371,28 @@ impl Add for Array { impl AddAssign for Array { fn add_assign(&mut self, rhs: Array) { - match Arc::try_unwrap(rhs.0) { - Ok(vec) => self.extend(vec), - Err(rc) => self.extend(rc.iter().cloned()), - } + self.0.extend(rhs.0); } } impl Extend for Array { fn extend>(&mut self, iter: T) { - Arc::make_mut(&mut self.0).extend(iter); + self.0.extend(iter); } } impl FromIterator for Array { fn from_iter>(iter: T) -> Self { - Self(Arc::new(iter.into_iter().collect())) + Self(iter.into_iter().collect()) } } impl IntoIterator for Array { type Item = Value; - type IntoIter = std::vec::IntoIter; + type IntoIter = ecow::IntoIter; fn into_iter(self) -> Self::IntoIter { - Arc::take(self.0).into_iter() + self.0.into_iter() } } diff --git a/src/model/cast.rs b/src/model/cast.rs index 0da9906f6..4c3005504 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -2,6 +2,8 @@ use std::num::NonZeroUsize; use std::ops::Add; use std::str::FromStr; +use ecow::EcoString; + use super::{ castable, Array, Content, Dict, Func, Label, Regex, Selector, Str, Transform, Value, }; @@ -13,7 +15,6 @@ use crate::geom::{ Rel, Sides, Smart, }; use crate::syntax::Spanned; -use crate::util::EcoString; /// Cast from a value to a specific type. pub trait Cast: Sized { diff --git a/src/model/content.rs b/src/model/content.rs index e6cb6d287..fde0a5cdd 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -6,6 +6,7 @@ use std::ops::{Add, AddAssign}; use std::sync::Arc; use comemo::Tracked; +use ecow::EcoString; use siphasher::sip128::{Hasher128, SipHasher}; use thin_vec::ThinVec; use typst_macros::node; @@ -16,7 +17,7 @@ use super::{ }; use crate::diag::{SourceResult, StrResult}; use crate::syntax::Span; -use crate::util::{EcoString, ReadableTypeId}; +use crate::util::ReadableTypeId; use crate::World; /// Composable representation of styled content. diff --git a/src/model/dict.rs b/src/model/dict.rs index 7165fbbe4..b6198b2c9 100644 --- a/src/model/dict.rs +++ b/src/model/dict.rs @@ -3,10 +3,12 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; +use ecow::{format_eco, EcoString}; + use super::{array, Array, Str, Value}; use crate::diag::StrResult; use crate::syntax::is_ident; -use crate::util::{format_eco, ArcExt, EcoString}; +use crate::util::ArcExt; /// Create a new [`Dict`] from key-value pairs. #[macro_export] diff --git a/src/model/eval.rs b/src/model/eval.rs index 6e118f8a9..7d48b5986 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -5,6 +5,7 @@ use std::mem; use std::path::{Path, PathBuf}; use comemo::{Track, Tracked, TrackedMut}; +use ecow::EcoVec; use unicode_segmentation::UnicodeSegmentation; use super::{ @@ -797,7 +798,7 @@ impl Eval for ast::Array { fn eval(&self, vm: &mut Vm) -> SourceResult { let items = self.items(); - let mut vec = Vec::with_capacity(items.size_hint().0); + 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)?), diff --git a/src/model/func.rs b/src/model/func.rs index c5bab64c6..216ee8063 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -3,6 +3,7 @@ use std::hash::{Hash, Hasher}; use std::sync::Arc; use comemo::{Track, Tracked, TrackedMut}; +use ecow::EcoString; use super::{ Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector, @@ -11,7 +12,6 @@ use super::{ use crate::diag::{bail, SourceResult, StrResult}; use crate::syntax::ast::{self, AstNode, Expr}; use crate::syntax::{SourceId, Span, SyntaxNode}; -use crate::util::EcoString; use crate::World; /// An evaluatable function. diff --git a/src/model/library.rs b/src/model/library.rs index c87ca095c..8ef22f10c 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -2,13 +2,14 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::num::NonZeroUsize; +use ecow::EcoString; use once_cell::sync::OnceCell; use super::{Content, Module, NodeId, StyleChain, StyleMap, Vt}; use crate::diag::SourceResult; use crate::doc::Document; use crate::geom::{Abs, Dir}; -use crate::util::{hash128, EcoString}; +use crate::util::hash128; /// Definition of Typst's standard library. #[derive(Debug, Clone, Hash)] diff --git a/src/model/methods.rs b/src/model/methods.rs index c0b636694..40e99de45 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -1,9 +1,10 @@ //! Methods on values. +use ecow::EcoString; + use super::{Args, Str, Value, Vm}; use crate::diag::{At, SourceResult}; use crate::syntax::Span; -use crate::util::EcoString; /// Call a method on a value. pub fn call( diff --git a/src/model/module.rs b/src/model/module.rs index 954a84f06..97c060e3d 100644 --- a/src/model/module.rs +++ b/src/model/module.rs @@ -1,9 +1,10 @@ use std::fmt::{self, Debug, Formatter}; use std::sync::Arc; +use ecow::{format_eco, EcoString}; + use super::{Content, Scope, Value}; use crate::diag::StrResult; -use crate::util::{format_eco, EcoString}; /// An evaluated module, ready for importing or typesetting. #[derive(Clone, Hash)] diff --git a/src/model/ops.rs b/src/model/ops.rs index 83137f38f..be7892f97 100644 --- a/src/model/ops.rs +++ b/src/model/ops.rs @@ -2,10 +2,11 @@ use std::cmp::Ordering; +use ecow::format_eco; + use super::{format_str, Regex, Value}; use crate::diag::StrResult; use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel, Smart}; -use crate::util::format_eco; use Value::*; /// Bail with a type mismatch error. diff --git a/src/model/scope.rs b/src/model/scope.rs index 18c75535c..f6bd2164e 100644 --- a/src/model/scope.rs +++ b/src/model/scope.rs @@ -2,9 +2,10 @@ use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; +use ecow::EcoString; + use super::{Func, FuncType, Library, Value}; use crate::diag::StrResult; -use crate::util::EcoString; /// A stack of scopes. #[derive(Debug, Default, Clone)] diff --git a/src/model/str.rs b/src/model/str.rs index 6bfbcebdd..228943dc9 100644 --- a/src/model/str.rs +++ b/src/model/str.rs @@ -3,24 +3,26 @@ use std::fmt::{self, Debug, Display, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::ops::{Add, AddAssign, Deref}; +use ecow::EcoString; use unicode_segmentation::UnicodeSegmentation; use super::{castable, dict, Array, Dict, Value}; use crate::diag::StrResult; use crate::geom::GenAlign; -use crate::util::{format_eco, EcoString}; /// Create a new [`Str`] from a format string. #[macro_export] #[doc(hidden)] macro_rules! __format_str { ($($tts:tt)*) => {{ - $crate::model::Str::from(format_eco!($($tts)*)) + $crate::model::Str::from($crate::model::format_eco!($($tts)*)) }}; } #[doc(inline)] pub use crate::__format_str as format_str; +#[doc(hidden)] +pub use ecow::format_eco; /// An immutable reference counted string. #[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] @@ -407,11 +409,13 @@ impl From for Str { Self(s.into()) } } + impl From> for Str { fn from(s: Cow) -> Self { Self(s.into()) } } + impl FromIterator for Str { fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) diff --git a/src/model/symbol.rs b/src/model/symbol.rs index 16ab58af9..fafebd995 100644 --- a/src/model/symbol.rs +++ b/src/model/symbol.rs @@ -3,8 +3,9 @@ use std::collections::BTreeSet; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::sync::Arc; +use ecow::EcoString; + use crate::diag::StrResult; -use crate::util::EcoString; #[doc(inline)] pub use typst_macros::symbols; diff --git a/src/model/value.rs b/src/model/value.rs index b860a3f66..e8cc42bc1 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -4,6 +4,7 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; +use ecow::{format_eco, EcoString}; use siphasher::sip128::{Hasher128, SipHasher}; use super::{ @@ -13,7 +14,6 @@ use super::{ use crate::diag::StrResult; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; use crate::syntax::{ast, Span}; -use crate::util::{format_eco, EcoString}; /// A computational value. #[derive(Clone)] diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index af7fedce9..6075b3b1c 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -5,13 +5,13 @@ 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::EcoString; /// A typed AST node. pub trait AstNode: Sized { diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index b79b4c7f4..bd168f0a3 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -1,9 +1,9 @@ +use ecow::{format_eco, EcoString}; use unicode_segmentation::UnicodeSegmentation; use unicode_xid::UnicodeXID; use unscanny::Scanner; use super::{ErrorPos, SyntaxKind}; -use crate::util::{format_eco, EcoString}; /// Splits up a string of source code into tokens. #[derive(Clone)] diff --git a/src/syntax/node.rs b/src/syntax/node.rs index e153b0bf2..d90a44ac8 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -3,10 +3,11 @@ use std::ops::{Deref, Range}; use std::rc::Rc; use std::sync::Arc; +use ecow::EcoString; + use super::ast::AstNode; use super::{SourceId, Span, SyntaxKind}; use crate::diag::SourceError; -use crate::util::EcoString; /// A node in the untyped syntax tree. #[derive(Clone, PartialEq, Hash)] diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index cd318983c..e5898e881 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -1,10 +1,10 @@ use std::collections::HashSet; use std::ops::Range; +use ecow::{format_eco, EcoString}; use unicode_math_class::MathClass; use super::{ast, is_newline, ErrorPos, LexMode, Lexer, SyntaxKind, SyntaxNode}; -use crate::util::{format_eco, EcoString}; /// Parse a source file. pub fn parse(text: &str) -> SyntaxNode { diff --git a/src/util/buffer.rs b/src/util/buffer.rs index 766b20845..da12b6cb3 100644 --- a/src/util/buffer.rs +++ b/src/util/buffer.rs @@ -6,7 +6,7 @@ use comemo::Prehashed; /// A shared buffer that is cheap to clone and hash. #[derive(Clone, Hash, Eq, PartialEq)] -pub struct Buffer(Prehashed>>); +pub struct Buffer(Arc>>); impl Buffer { /// Return a view into the buffer. @@ -22,19 +22,13 @@ impl Buffer { impl From<&[u8]> for Buffer { fn from(slice: &[u8]) -> Self { - Self(Prehashed::new(Arc::new(slice.to_vec()))) + Self(Arc::new(Prehashed::new(slice.to_vec()))) } } impl From> for Buffer { fn from(vec: Vec) -> Self { - Self(Prehashed::new(Arc::new(vec))) - } -} - -impl From>> for Buffer { - fn from(arc: Arc>) -> Self { - Self(Prehashed::new(arc)) + Self(Arc::new(Prehashed::new(vec))) } } diff --git a/src/util/eco.rs b/src/util/eco.rs deleted file mode 100644 index ceaac2f57..000000000 --- a/src/util/eco.rs +++ /dev/null @@ -1,515 +0,0 @@ -use std::borrow::{Borrow, Cow}; -use std::cmp::Ordering; -use std::fmt::{self, Debug, Display, Formatter, Write}; -use std::hash::{Hash, Hasher}; -use std::ops::{Add, AddAssign, Deref}; -use std::sync::Arc; - -use super::ArcExt; - -/// Create a new [`EcoString`] from a format string. -#[macro_export] -#[doc(hidden)] -macro_rules! __format_eco { - ($($tts:tt)*) => {{ - use std::fmt::Write; - let mut s = $crate::util::EcoString::new(); - write!(s, $($tts)*).unwrap(); - s - }}; -} - -#[doc(inline)] -pub use crate::__format_eco as format_eco; - -/// An economical string with inline storage and clone-on-write semantics. -#[derive(Clone)] -pub struct EcoString(Repr); - -/// The internal representation. Either: -/// - inline when below a certain number of bytes, or -/// - reference-counted on the heap with clone-on-write semantics. -#[derive(Clone)] -enum Repr { - Small { buf: [u8; LIMIT], len: u8 }, - Large(Arc), -} - -/// The maximum number of bytes that can be stored inline. -/// -/// The value is chosen such that an `EcoString` fits exactly into 16 bytes -/// (which are needed anyway due to the `Arc`s alignment, at least on 64-bit -/// platforms). -/// -/// Must be at least 4 to hold any char. -const LIMIT: usize = 14; - -impl EcoString { - /// Create a new, empty string. - pub const fn new() -> Self { - Self(Repr::Small { buf: [0; LIMIT], len: 0 }) - } - - /// Create a new, empty string with the given `capacity`. - pub fn with_capacity(capacity: usize) -> Self { - if capacity <= LIMIT { - Self::new() - } else { - Self(Repr::Large(Arc::new(String::with_capacity(capacity)))) - } - } - - /// Create an instance from an existing string-like type. - pub fn from_str_like(s: S) -> Self - where - S: AsRef + Into, - { - let slice = s.as_ref(); - let len = slice.len(); - Self(if len <= LIMIT { - let mut buf = [0; LIMIT]; - buf[..len].copy_from_slice(slice.as_bytes()); - Repr::Small { buf, len: len as u8 } - } else { - Repr::Large(Arc::new(s.into())) - }) - } - - /// Whether the string is empty. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// The length of the string in bytes. - pub fn len(&self) -> usize { - match &self.0 { - Repr::Small { len, .. } => usize::from(*len), - Repr::Large(string) => string.len(), - } - } - - /// A string slice containing the entire string. - pub fn as_str(&self) -> &str { - self - } - - /// Append the given character at the end. - pub fn push(&mut self, c: char) { - match &mut self.0 { - Repr::Small { buf, len } => { - let prev = usize::from(*len); - if c.len_utf8() == 1 && prev < LIMIT { - buf[prev] = c as u8; - *len += 1; - } else { - self.push_str(c.encode_utf8(&mut [0; 4])); - } - } - Repr::Large(rc) => Arc::make_mut(rc).push(c), - } - } - - /// Append the given string slice at the end. - pub fn push_str(&mut self, string: &str) { - match &mut self.0 { - Repr::Small { buf, len } => { - let prev = usize::from(*len); - let new = prev + string.len(); - if new <= LIMIT { - buf[prev..new].copy_from_slice(string.as_bytes()); - *len = new as u8; - } else { - let mut spilled = String::with_capacity(new); - spilled.push_str(self); - spilled.push_str(string); - self.0 = Repr::Large(Arc::new(spilled)); - } - } - Repr::Large(rc) => Arc::make_mut(rc).push_str(string), - } - } - - /// Remove the last character from the string. - pub fn pop(&mut self) -> Option { - let c = self.as_str().chars().rev().next()?; - match &mut self.0 { - Repr::Small { len, .. } => { - *len -= c.len_utf8() as u8; - } - Repr::Large(rc) => { - Arc::make_mut(rc).pop(); - } - } - Some(c) - } - - /// Clear the string. - pub fn clear(&mut self) { - match &mut self.0 { - Repr::Small { len, .. } => *len = 0, - Repr::Large(rc) => { - if Arc::strong_count(rc) == 1 { - Arc::make_mut(rc).clear(); - } else { - *self = Self::new(); - } - } - } - } - - /// Convert the string to lowercase. - pub fn to_lowercase(&self) -> Self { - if let Repr::Small { mut buf, len } = self.0 { - if self.is_ascii() { - buf[..usize::from(len)].make_ascii_lowercase(); - return Self(Repr::Small { buf, len }); - } - } - - self.as_str().to_lowercase().into() - } - - /// Convert the string to uppercase. - pub fn to_uppercase(&self) -> Self { - if let Repr::Small { mut buf, len } = self.0 { - if self.is_ascii() { - buf[..usize::from(len)].make_ascii_uppercase(); - return Self(Repr::Small { buf, len }); - } - } - - self.as_str().to_uppercase().into() - } - - /// Repeat this string `n` times. - pub fn repeat(&self, n: usize) -> Self { - if n == 0 { - return Self::new(); - } - - if let Repr::Small { buf, len } = self.0 { - let prev = usize::from(len); - let new = prev.saturating_mul(n); - if new <= LIMIT { - let src = &buf[..prev]; - let mut buf = [0; LIMIT]; - for i in 0..n { - buf[prev * i..prev * (i + 1)].copy_from_slice(src); - } - return Self(Repr::Small { buf, len: new as u8 }); - } - } - - self.as_str().repeat(n).into() - } -} - -impl Deref for EcoString { - type Target = str; - - fn deref(&self) -> &str { - match &self.0 { - // Safety: - // The buffer contents stem from correct UTF-8 sources: - // - Valid ASCII characters - // - Other string slices - // - Chars that were encoded with char::encode_utf8 - // Furthermore, we still do the bounds-check on the len in case - // it gets corrupted somehow. - Repr::Small { buf, len } => unsafe { - std::str::from_utf8_unchecked(&buf[..usize::from(*len)]) - }, - Repr::Large(string) => string.as_str(), - } - } -} - -impl Default for EcoString { - fn default() -> Self { - Self::new() - } -} - -impl Debug for EcoString { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(self.as_str(), f) - } -} - -impl Display for EcoString { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(self.as_str(), f) - } -} - -impl Eq for EcoString {} - -impl PartialEq for EcoString { - fn eq(&self, other: &Self) -> bool { - self.as_str().eq(other.as_str()) - } -} - -impl PartialEq for EcoString { - fn eq(&self, other: &str) -> bool { - self.as_str().eq(other) - } -} - -impl PartialEq<&str> for EcoString { - fn eq(&self, other: &&str) -> bool { - self.as_str().eq(*other) - } -} - -impl PartialEq for str { - fn eq(&self, other: &EcoString) -> bool { - self.eq(other.as_str()) - } -} - -impl PartialEq for &str { - fn eq(&self, other: &EcoString) -> bool { - (*self).eq(other.as_str()) - } -} - -impl Ord for EcoString { - fn cmp(&self, other: &Self) -> Ordering { - self.as_str().cmp(other.as_str()) - } -} - -impl PartialOrd for EcoString { - fn partial_cmp(&self, other: &Self) -> Option { - self.as_str().partial_cmp(other.as_str()) - } -} - -impl Hash for EcoString { - fn hash(&self, state: &mut H) { - self.as_str().hash(state); - } -} - -impl Write for EcoString { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.push_str(s); - Ok(()) - } - - fn write_char(&mut self, c: char) -> fmt::Result { - self.push(c); - Ok(()) - } -} - -impl Add for EcoString { - type Output = Self; - - fn add(mut self, rhs: Self) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for EcoString { - fn add_assign(&mut self, rhs: Self) { - self.push_str(rhs.as_str()); - } -} - -impl AsRef for EcoString { - fn as_ref(&self) -> &str { - self - } -} - -impl Borrow for EcoString { - fn borrow(&self) -> &str { - self - } -} - -impl From for EcoString { - fn from(c: char) -> Self { - let mut buf = [0; LIMIT]; - let len = c.encode_utf8(&mut buf).len(); - Self(Repr::Small { buf, len: len as u8 }) - } -} - -impl From<&str> for EcoString { - fn from(s: &str) -> Self { - Self::from_str_like(s) - } -} - -impl From for EcoString { - fn from(s: String) -> Self { - Self::from_str_like(s) - } -} - -impl From> for EcoString { - fn from(s: Cow) -> Self { - match s { - Cow::Borrowed(s) => s.into(), - Cow::Owned(s) => s.into(), - } - } -} - -impl FromIterator for EcoString { - fn from_iter>(iter: T) -> Self { - let mut s = Self::new(); - for c in iter { - s.push(c); - } - s - } -} - -impl FromIterator for EcoString { - fn from_iter>(iter: T) -> Self { - let mut s = Self::new(); - for piece in iter { - s.push_str(&piece); - } - s - } -} - -impl Extend for EcoString { - fn extend>(&mut self, iter: T) { - for c in iter { - self.push(c); - } - } -} - -impl From for String { - fn from(s: EcoString) -> Self { - match s.0 { - Repr::Small { .. } => s.as_str().to_owned(), - Repr::Large(rc) => Arc::take(rc), - } - } -} - -impl From<&EcoString> for String { - fn from(s: &EcoString) -> Self { - s.as_str().to_owned() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const ALPH: &str = "abcdefghijklmnopqrstuvwxyz"; - - #[test] - fn test_str_new() { - // Test inline strings. - assert_eq!(EcoString::new(), ""); - assert_eq!(EcoString::from('a'), "a"); - assert_eq!(EcoString::from('😀'), "😀"); - assert_eq!(EcoString::from("abc"), "abc"); - - // Test around the inline limit. - assert_eq!(EcoString::from(&ALPH[..LIMIT - 1]), ALPH[..LIMIT - 1]); - assert_eq!(EcoString::from(&ALPH[..LIMIT]), ALPH[..LIMIT]); - assert_eq!(EcoString::from(&ALPH[..LIMIT + 1]), ALPH[..LIMIT + 1]); - - // Test heap string. - assert_eq!(EcoString::from(ALPH), ALPH); - } - - #[test] - fn test_str_push() { - let mut v = EcoString::new(); - v.push('a'); - v.push('b'); - v.push_str("cd😀"); - assert_eq!(v, "abcd😀"); - assert_eq!(v.len(), 8); - - // Test fully filling the inline storage. - v.push_str("efghij"); - assert_eq!(v.len(), LIMIT); - - // Test spilling with `push`. - let mut a = v.clone(); - a.push('k'); - assert_eq!(a, "abcd😀efghijk"); - assert_eq!(a.len(), 15); - - // Test spilling with `push_str`. - let mut b = v.clone(); - b.push_str("klmn"); - assert_eq!(b, "abcd😀efghijklmn"); - assert_eq!(b.len(), 18); - - // v should be unchanged. - assert_eq!(v.len(), LIMIT); - } - - #[test] - fn test_str_pop() { - // Test with inline string. - let mut v = EcoString::from("Hello World!"); - assert_eq!(v.pop(), Some('!')); - assert_eq!(v, "Hello World"); - - // Remove one-by-one. - for _ in 0..10 { - v.pop(); - } - - assert_eq!(v, "H"); - assert_eq!(v.pop(), Some('H')); - assert_eq!(v, ""); - assert!(v.is_empty()); - - // Test with large string. - let mut v = EcoString::from(ALPH); - assert_eq!(v.pop(), Some('z')); - assert_eq!(v.len(), 25); - } - - #[test] - fn test_str_index() { - // Test that we can use the index syntax. - let v = EcoString::from("abc"); - assert_eq!(&v[..2], "ab"); - } - - #[test] - fn test_str_case() { - assert_eq!(EcoString::new().to_uppercase(), ""); - assert_eq!(EcoString::from("abc").to_uppercase(), "ABC"); - assert_eq!(EcoString::from("AΣ").to_lowercase(), "aς"); - assert_eq!( - EcoString::from("a").repeat(100).to_uppercase(), - EcoString::from("A").repeat(100) - ); - assert_eq!( - EcoString::from("Ö").repeat(20).to_lowercase(), - EcoString::from("ö").repeat(20) - ); - } - - #[test] - fn test_str_repeat() { - // Test with empty string. - assert_eq!(EcoString::new().repeat(0), ""); - assert_eq!(EcoString::new().repeat(100), ""); - - // Test non-spilling and spilling case. - let v = EcoString::from("abc"); - assert_eq!(v.repeat(0), ""); - assert_eq!(v.repeat(3), "abcabcabc"); - assert_eq!(v.repeat(5), "abcabcabcabcabc"); - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs index b20dd0536..ce0ed1aac 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,12 +2,9 @@ pub mod fat; -#[macro_use] -mod eco; mod buffer; pub use buffer::Buffer; -pub use eco::{format_eco, EcoString}; use std::any::TypeId; use std::fmt::{self, Debug, Formatter};