Switch to ecow

This commit is contained in:
Laurenz 2023-02-23 12:15:38 +01:00
parent 6e65ebf236
commit a1d47695a2
35 changed files with 96 additions and 593 deletions

10
Cargo.lock generated
View File

@ -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"

View File

@ -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

View File

@ -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" }

View File

@ -458,12 +458,12 @@ pub fn range(args: &mut Args) -> SourceResult<Value> {
};
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))
}

View File

@ -86,15 +86,15 @@ pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
}
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.

View File

@ -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)]

View File

@ -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::*;

View File

@ -1,5 +1,4 @@
use typst::model::SequenceNode;
use typst::util::EcoString;
use super::{variant, SpaceNode, TextNode, TextSize};
use crate::prelude::*;

View File

@ -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<T> = Result<T, Box<Vec<SourceError>>>;

View File

@ -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)]

View File

@ -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) {

View File

@ -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)]

View File

@ -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]) {

View File

@ -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.

View File

@ -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<Vec<Value>>);
pub struct Array(EcoVec<Value>);
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<Value>) -> Self {
Self(Arc::new(vec))
/// Create a new array from an eco vector of values.
pub fn from_vec(vec: EcoVec<Value>) -> 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<Value> {
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::<bool>().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<Self> {
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<Value> for Array {
fn extend<T: IntoIterator<Item = Value>>(&mut self, iter: T) {
Arc::make_mut(&mut self.0).extend(iter);
self.0.extend(iter);
}
}
impl FromIterator<Value> for Array {
fn from_iter<T: IntoIterator<Item = Value>>(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<Value>;
type IntoIter = ecow::IntoIter<Value>;
fn into_iter(self) -> Self::IntoIter {
Arc::take(self.0).into_iter()
self.0.into_iter()
}
}

View File

@ -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<V = Value>: Sized {

View File

@ -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.

View File

@ -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]

View File

@ -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<Self::Output> {
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)?),

View File

@ -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.

View File

@ -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)]

View File

@ -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(

View File

@ -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)]

View File

@ -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.

View File

@ -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)]

View File

@ -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<String> for Str {
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())

View File

@ -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;

View File

@ -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)]

View File

@ -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 {

View File

@ -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)]

View File

@ -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)]

View File

@ -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 {

View File

@ -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<Arc<Vec<u8>>>);
pub struct Buffer(Arc<Prehashed<Vec<u8>>>);
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<Vec<u8>> for Buffer {
fn from(vec: Vec<u8>) -> Self {
Self(Prehashed::new(Arc::new(vec)))
}
}
impl From<Arc<Vec<u8>>> for Buffer {
fn from(arc: Arc<Vec<u8>>) -> Self {
Self(Prehashed::new(arc))
Self(Arc::new(Prehashed::new(vec)))
}
}

View File

@ -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<String>),
}
/// 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: S) -> Self
where
S: AsRef<str> + Into<String>,
{
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<char> {
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<str> 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<EcoString> for str {
fn eq(&self, other: &EcoString) -> bool {
self.eq(other.as_str())
}
}
impl PartialEq<EcoString> 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<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
impl Hash for EcoString {
fn hash<H: Hasher>(&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<str> for EcoString {
fn as_ref(&self) -> &str {
self
}
}
impl Borrow<str> for EcoString {
fn borrow(&self) -> &str {
self
}
}
impl From<char> 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<String> for EcoString {
fn from(s: String) -> Self {
Self::from_str_like(s)
}
}
impl From<Cow<'_, str>> for EcoString {
fn from(s: Cow<str>) -> Self {
match s {
Cow::Borrowed(s) => s.into(),
Cow::Owned(s) => s.into(),
}
}
}
impl FromIterator<char> for EcoString {
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
let mut s = Self::new();
for c in iter {
s.push(c);
}
s
}
}
impl FromIterator<Self> for EcoString {
fn from_iter<T: IntoIterator<Item = Self>>(iter: T) -> Self {
let mut s = Self::new();
for piece in iter {
s.push_str(&piece);
}
s
}
}
impl Extend<char> for EcoString {
fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
for c in iter {
self.push(c);
}
}
}
impl From<EcoString> 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("").to_lowercase(), "");
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");
}
}

View File

@ -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};