Separate type for string values

This commit is contained in:
Laurenz 2021-08-14 15:24:59 +02:00
parent fcb4e45118
commit 6ae6d86b9c
25 changed files with 565 additions and 528 deletions

View File

@ -1,5 +1,5 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Display, Formatter, Write};
use std::iter::FromIterator; use std::iter::FromIterator;
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
use std::rc::Rc; use std::rc::Rc;
@ -19,8 +19,8 @@ macro_rules! array {
}; };
} }
/// A variably-typed array with clone-on-write value semantics. /// An array of values with clone-on-write value semantics.
#[derive(Clone, PartialEq)] #[derive(Default, Clone, PartialEq)]
pub struct Array { pub struct Array {
vec: Rc<Vec<Value>>, vec: Rc<Vec<Value>>,
} }
@ -28,7 +28,7 @@ pub struct Array {
impl Array { impl Array {
/// Create a new, empty array. /// Create a new, empty array.
pub fn new() -> Self { pub fn new() -> Self {
Self { vec: Rc::new(vec![]) } Self::default()
} }
/// Create a new array from a vector of values. /// Create a new array from a vector of values.
@ -36,16 +36,9 @@ impl Array {
Self { vec: Rc::new(vec) } Self { vec: Rc::new(vec) }
} }
/// Create a new, empty array with the given `capacity`.
pub fn with_capacity(capacity: usize) -> Self {
Self {
vec: Rc::new(Vec::with_capacity(capacity)),
}
}
/// Whether the array is empty. /// Whether the array is empty.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len() == 0 self.vec.is_empty()
} }
/// The length of the array. /// The length of the array.
@ -106,18 +99,28 @@ fn out_of_bounds(index: i64, len: i64) -> String {
format!("array index out of bounds (index: {}, len: {})", index, len) format!("array index out of bounds (index: {}, len: {})", index, len)
} }
impl Default for Array {
fn default() -> Self {
Self::new()
}
}
impl Debug for Array { impl Debug for Array {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_list().entries(self.vec.iter()).finish() f.debug_list().entries(self.vec.iter()).finish()
} }
} }
impl Display for Array {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_char('(')?;
for (i, value) in self.iter().enumerate() {
Display::fmt(value, f)?;
if i + 1 < self.vec.len() {
f.write_str(", ")?;
}
}
if self.len() == 1 {
f.write_char(',')?;
}
f.write_char(')')
}
}
impl Add for Array { impl Add for Array {
type Output = Self; type Output = Self;

View File

@ -1,13 +1,11 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Display, Formatter, Write};
use std::iter::FromIterator; use std::iter::FromIterator;
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
use std::rc::Rc; use std::rc::Rc;
use super::Value; use super::{Str, Value};
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::pretty::pretty;
use crate::util::EcoString;
/// Create a new [`Dict`] from key-value pairs. /// Create a new [`Dict`] from key-value pairs.
#[allow(unused_macros)] #[allow(unused_macros)]
@ -15,15 +13,15 @@ macro_rules! dict {
($($key:expr => $value:expr),* $(,)?) => {{ ($($key:expr => $value:expr),* $(,)?) => {{
#[allow(unused_mut)] #[allow(unused_mut)]
let mut map = std::collections::BTreeMap::new(); let mut map = std::collections::BTreeMap::new();
$(map.insert($crate::util::EcoString::from($key), $crate::eval::Value::from($value));)* $(map.insert($crate::eval::Str::from($key), $crate::eval::Value::from($value));)*
$crate::eval::Dict::from_map(map) $crate::eval::Dict::from_map(map)
}}; }};
} }
/// A variably-typed dictionary with clone-on-write value semantics. /// A dictionary from strings to values with clone-on-write value semantics.
#[derive(Clone, PartialEq)] #[derive(Default, Clone, PartialEq)]
pub struct Dict { pub struct Dict {
map: Rc<BTreeMap<EcoString, Value>>, map: Rc<BTreeMap<Str, Value>>,
} }
impl Dict { impl Dict {
@ -33,13 +31,13 @@ impl Dict {
} }
/// Create a new dictionary from a mapping of strings to values. /// Create a new dictionary from a mapping of strings to values.
pub fn from_map(map: BTreeMap<EcoString, Value>) -> Self { pub fn from_map(map: BTreeMap<Str, Value>) -> Self {
Self { map: Rc::new(map) } Self { map: Rc::new(map) }
} }
/// Whether the dictionary is empty. /// Whether the dictionary is empty.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len() == 0 self.map.is_empty()
} }
/// The number of pairs in the dictionary. /// The number of pairs in the dictionary.
@ -48,21 +46,21 @@ impl Dict {
} }
/// Borrow the value the given `key` maps to. /// Borrow the value the given `key` maps to.
pub fn get(&self, key: &str) -> StrResult<&Value> { pub fn get(&self, key: Str) -> StrResult<&Value> {
self.map.get(key).ok_or_else(|| missing_key(key)) self.map.get(&key).ok_or_else(|| missing_key(&key))
} }
/// Mutably borrow the value the given `key` maps to. /// Mutably borrow the value the given `key` maps to.
/// ///
/// This inserts the key with [`None`](Value::None) as the value if not /// This inserts the key with [`None`](Value::None) as the value if not
/// present so far. /// present so far.
pub fn get_mut(&mut self, key: EcoString) -> &mut Value { pub fn get_mut(&mut self, key: Str) -> &mut Value {
Rc::make_mut(&mut self.map).entry(key).or_default() Rc::make_mut(&mut self.map).entry(key.into()).or_default()
} }
/// Insert a mapping from the given `key` to the given `value`. /// Insert a mapping from the given `key` to the given `value`.
pub fn insert(&mut self, key: EcoString, value: Value) { pub fn insert(&mut self, key: Str, value: Value) {
Rc::make_mut(&mut self.map).insert(key, value); Rc::make_mut(&mut self.map).insert(key.into(), value);
} }
/// Clear the dictionary. /// Clear the dictionary.
@ -75,20 +73,32 @@ impl Dict {
} }
/// Iterate over pairs of references to the contained keys and values. /// Iterate over pairs of references to the contained keys and values.
pub fn iter(&self) -> std::collections::btree_map::Iter<EcoString, Value> { pub fn iter(&self) -> std::collections::btree_map::Iter<Str, Value> {
self.map.iter() self.map.iter()
} }
} }
/// The missing key access error message. /// The missing key access error message.
#[cold] #[cold]
fn missing_key(key: &str) -> String { fn missing_key(key: &Str) -> String {
format!("dictionary does not contain key: {}", pretty(key)) format!("dictionary does not contain key: {}", key)
} }
impl Default for Dict { impl Display for Dict {
fn default() -> Self { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Self::new() f.write_char('(')?;
if self.is_empty() {
f.write_char(':')?;
}
for (i, (key, value)) in self.iter().enumerate() {
f.write_str(key)?;
f.write_str(": ")?;
Display::fmt(value, f)?;
if i + 1 < self.map.len() {
f.write_str(", ")?;
}
}
f.write_char(')')
} }
} }
@ -116,21 +126,21 @@ impl AddAssign for Dict {
} }
} }
impl FromIterator<(EcoString, Value)> for Dict { impl FromIterator<(Str, Value)> for Dict {
fn from_iter<T: IntoIterator<Item = (EcoString, Value)>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self {
Dict { map: Rc::new(iter.into_iter().collect()) } Dict { map: Rc::new(iter.into_iter().collect()) }
} }
} }
impl Extend<(EcoString, Value)> for Dict { impl Extend<(Str, Value)> for Dict {
fn extend<T: IntoIterator<Item = (EcoString, Value)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) {
Rc::make_mut(&mut self.map).extend(iter); Rc::make_mut(&mut self.map).extend(iter);
} }
} }
impl IntoIterator for Dict { impl IntoIterator for Dict {
type Item = (EcoString, Value); type Item = (Str, Value);
type IntoIter = std::collections::btree_map::IntoIter<EcoString, Value>; type IntoIter = std::collections::btree_map::IntoIter<Str, Value>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
match Rc::try_unwrap(self.map) { match Rc::try_unwrap(self.map) {
@ -141,8 +151,8 @@ impl IntoIterator for Dict {
} }
impl<'a> IntoIterator for &'a Dict { impl<'a> IntoIterator for &'a Dict {
type Item = (&'a EcoString, &'a Value); type Item = (&'a Str, &'a Value);
type IntoIter = std::collections::btree_map::Iter<'a, EcoString, Value>; type IntoIter = std::collections::btree_map::Iter<'a, Str, Value>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.iter() self.iter()

View File

@ -1,15 +1,17 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Display, Formatter, Write};
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use super::{Cast, EvalContext, Value}; use super::{Cast, EvalContext, Str, Value};
use crate::diag::{At, TypResult}; use crate::diag::{At, TypResult};
use crate::syntax::{Span, Spanned}; use crate::syntax::{Span, Spanned};
use crate::util::EcoString; use crate::util::EcoString;
/// An evaluatable function. /// An evaluatable function.
#[derive(Clone)] #[derive(Clone)]
pub struct Function(Rc<Repr<Func>>); pub struct Function {
repr: Rc<Repr<Func>>,
}
/// The unsized representation behind the [`Rc`]. /// The unsized representation behind the [`Rc`].
struct Repr<T: ?Sized> { struct Repr<T: ?Sized> {
@ -17,20 +19,20 @@ struct Repr<T: ?Sized> {
func: T, func: T,
} }
type Func = dyn Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value>; type Func = dyn Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value>;
impl Function { impl Function {
/// Create a new function from a rust closure. /// Create a new function from a rust closure.
pub fn new<F>(name: Option<EcoString>, func: F) -> Self pub fn new<F>(name: Option<EcoString>, func: F) -> Self
where where
F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value> + 'static, F: Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value> + 'static,
{ {
Self(Rc::new(Repr { name, func })) Self { repr: Rc::new(Repr { name, func }) }
} }
/// The name of the function. /// The name of the function.
pub fn name(&self) -> Option<&EcoString> { pub fn name(&self) -> Option<&EcoString> {
self.0.name.as_ref() self.repr.name.as_ref()
} }
} }
@ -38,44 +40,55 @@ impl Deref for Function {
type Target = Func; type Target = Func;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0.func &self.repr.func
}
}
impl Display for Function {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("<function")?;
if let Some(name) = self.name() {
f.write_char(' ')?;
f.write_str(name)?;
}
f.write_char('>')
} }
} }
impl Debug for Function { impl Debug for Function {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("ValueFunc").field("name", &self.0.name).finish() f.debug_struct("Function").field("name", &self.repr.name).finish()
} }
} }
impl PartialEq for Function { impl PartialEq for Function {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// We cast to thin pointers for comparison. // We cast to thin pointers for comparison.
Rc::as_ptr(&self.0) as *const () == Rc::as_ptr(&other.0) as *const () Rc::as_ptr(&self.repr) as *const () == Rc::as_ptr(&other.repr) as *const ()
} }
} }
/// Evaluated arguments to a function. /// Evaluated arguments to a function.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct FuncArgs { pub struct Arguments {
/// The span of the whole argument list. /// The span of the whole argument list.
pub span: Span, pub span: Span,
/// The positional and named arguments. /// The positional and named arguments.
pub items: Vec<FuncArg>, pub items: Vec<Argument>,
} }
/// An argument to a function call: `12` or `draw: false`. /// An argument to a function call: `12` or `draw: false`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct FuncArg { pub struct Argument {
/// The span of the whole argument. /// The span of the whole argument.
pub span: Span, pub span: Span,
/// The name of the argument (`None` for positional arguments). /// The name of the argument (`None` for positional arguments).
pub name: Option<EcoString>, pub name: Option<Str>,
/// The value of the argument. /// The value of the argument.
pub value: Spanned<Value>, pub value: Spanned<Value>,
} }
impl FuncArgs { impl Arguments {
/// Find and consume the first castable positional argument. /// Find and consume the first castable positional argument.
pub fn eat<T>(&mut self) -> Option<T> pub fn eat<T>(&mut self) -> Option<T>
where where
@ -150,16 +163,14 @@ impl FuncArgs {
} }
Ok(()) Ok(())
} }
}
impl FuncArgs {
/// Reinterpret these arguments as actually being an array index. /// Reinterpret these arguments as actually being an array index.
pub fn into_index(self) -> TypResult<i64> { pub fn into_index(self) -> TypResult<i64> {
self.into_castable("index") self.into_castable("index")
} }
/// Reinterpret these arguments as actually being a dictionary key. /// Reinterpret these arguments as actually being a dictionary key.
pub fn into_key(self) -> TypResult<EcoString> { pub fn into_key(self) -> TypResult<Str> {
self.into_castable("key") self.into_castable("key")
} }
@ -170,11 +181,11 @@ impl FuncArgs {
{ {
let mut iter = self.items.into_iter(); let mut iter = self.items.into_iter();
let value = match iter.next() { let value = match iter.next() {
Some(FuncArg { name: None, value, .. }) => value.v.cast().at(value.span)?, Some(Argument { name: None, value, .. }) => value.v.cast().at(value.span)?,
None => { None => {
bail!(self.span, "missing {}", what); bail!(self.span, "missing {}", what);
} }
Some(FuncArg { name: Some(_), span, .. }) => { Some(Argument { name: Some(_), span, .. }) => {
bail!(span, "named pair is not allowed here"); bail!(span, "named pair is not allowed here");
} }
}; };
@ -186,3 +197,24 @@ impl FuncArgs {
Ok(value) Ok(value)
} }
} }
impl Display for Arguments {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_char('(')?;
for (i, arg) in self.items.iter().enumerate() {
if let Some(name) = &arg.name {
f.write_str(name)?;
f.write_str(": ")?;
}
Display::fmt(&arg.value.v, f)?;
if i + 1 < self.items.len() {
f.write_str(", ")?;
}
}
f.write_char(')')
}
}
dynamic! {
Arguments: "arguments",
}

View File

@ -10,8 +10,10 @@ mod capture;
mod function; mod function;
mod ops; mod ops;
mod scope; mod scope;
mod str;
mod template; mod template;
pub use self::str::*;
pub use array::*; pub use array::*;
pub use capture::*; pub use capture::*;
pub use dict::*; pub use dict::*;
@ -35,7 +37,7 @@ use crate::parse::parse;
use crate::source::{SourceId, SourceStore}; use crate::source::{SourceId, SourceStore};
use crate::syntax::visit::Visit; use crate::syntax::visit::Visit;
use crate::syntax::*; use crate::syntax::*;
use crate::util::{EcoString, RefMutExt}; use crate::util::RefMutExt;
use crate::Context; use crate::Context;
/// Evaluate a parsed source file into a module. /// Evaluate a parsed source file into a module.
@ -214,7 +216,7 @@ impl Eval for Lit {
Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)), Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)),
Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)), Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)),
Self::Fractional(_, v) => Value::Fractional(Fractional::new(v)), Self::Fractional(_, v) => Value::Fractional(Fractional::new(v)),
Self::Str(_, ref v) => Value::Str(v.clone()), Self::Str(_, ref v) => Value::Str(v.into()),
}) })
} }
} }
@ -244,7 +246,7 @@ impl Eval for DictExpr {
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
self.items self.items
.iter() .iter()
.map(|Named { name, expr }| Ok((name.string.clone(), expr.eval(ctx)?))) .map(|Named { name, expr }| Ok(((&name.string).into(), expr.eval(ctx)?)))
.collect() .collect()
} }
} }
@ -373,7 +375,7 @@ impl Eval for CallExpr {
} }
Value::Dict(dict) => { Value::Dict(dict) => {
dict.get(&args.into_key()?).map(Value::clone).at(self.span) dict.get(args.into_key()?).map(Value::clone).at(self.span)
} }
Value::Func(func) => { Value::Func(func) => {
@ -393,7 +395,7 @@ impl Eval for CallExpr {
} }
impl Eval for CallArgs { impl Eval for CallArgs {
type Output = FuncArgs; type Output = Arguments;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
let mut items = Vec::with_capacity(self.items.len()); let mut items = Vec::with_capacity(self.items.len());
@ -402,43 +404,49 @@ impl Eval for CallArgs {
let span = arg.span(); let span = arg.span();
match arg { match arg {
CallArg::Pos(expr) => { CallArg::Pos(expr) => {
items.push(FuncArg { items.push(Argument {
span, span,
name: None, name: None,
value: Spanned::new(expr.eval(ctx)?, expr.span()), value: Spanned::new(expr.eval(ctx)?, expr.span()),
}); });
} }
CallArg::Named(Named { name, expr }) => { CallArg::Named(Named { name, expr }) => {
items.push(FuncArg { items.push(Argument {
span, span,
name: Some(name.string.clone()), name: Some((&name.string).into()),
value: Spanned::new(expr.eval(ctx)?, expr.span()), value: Spanned::new(expr.eval(ctx)?, expr.span()),
}); });
} }
CallArg::Spread(expr) => match expr.eval(ctx)? { CallArg::Spread(expr) => match expr.eval(ctx)? {
Value::Args(args) => {
items.extend(args.items.iter().cloned());
}
Value::Array(array) => { Value::Array(array) => {
items.extend(array.into_iter().map(|value| FuncArg { items.extend(array.into_iter().map(|value| Argument {
span, span,
name: None, name: None,
value: Spanned::new(value, span), value: Spanned::new(value, span),
})); }));
} }
Value::Dict(dict) => { Value::Dict(dict) => {
items.extend(dict.into_iter().map(|(key, value)| FuncArg { items.extend(dict.into_iter().map(|(key, value)| Argument {
span, span,
name: Some(key), name: Some(key),
value: Spanned::new(value, span), value: Spanned::new(value, span),
})); }));
} }
v => bail!(expr.span(), "cannot spread {}", v.type_name()), v => {
if let Value::Dyn(dynamic) = &v {
if let Some(args) = dynamic.downcast_ref::<Arguments>() {
items.extend(args.items.iter().cloned());
continue;
}
}
bail!(expr.span(), "cannot spread {}", v.type_name())
}
}, },
} }
} }
Ok(FuncArgs { span: self.span, items }) Ok(Arguments { span: self.span, items })
} }
} }
@ -499,7 +507,7 @@ impl Eval for ClosureExpr {
// Put the remaining arguments into the sink. // Put the remaining arguments into the sink.
if let Some(sink) = &sink { if let Some(sink) = &sink {
ctx.scopes.def_mut(sink, Rc::new(args.take())); ctx.scopes.def_mut(sink, args.take());
} }
let value = body.eval(ctx)?; let value = body.eval(ctx)?;
@ -599,7 +607,7 @@ impl Eval for ForExpr {
let iter = self.iter.eval(ctx)?; let iter = self.iter.eval(ctx)?;
match (&self.pattern, iter) { match (&self.pattern, iter) {
(ForPattern::Value(v), Value::Str(string)) => { (ForPattern::Value(v), Value::Str(string)) => {
iter!(for (v => value) in string.chars().map(|c| Value::Str(c.into()))) iter!(for (v => value) in string.iter())
} }
(ForPattern::Value(v), Value::Array(array)) => { (ForPattern::Value(v), Value::Array(array)) => {
iter!(for (v => value) in array.into_iter()) iter!(for (v => value) in array.into_iter())
@ -627,7 +635,7 @@ impl Eval for ImportExpr {
type Output = Value; type Output = Value;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
let path = self.path.eval(ctx)?.cast::<EcoString>().at(self.path.span())?; let path = self.path.eval(ctx)?.cast::<Str>().at(self.path.span())?;
let file = ctx.import(&path, self.path.span())?; let file = ctx.import(&path, self.path.span())?;
let module = &ctx.modules[&file]; let module = &ctx.modules[&file];
@ -657,7 +665,7 @@ impl Eval for IncludeExpr {
type Output = Value; type Output = Value;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
let path = self.path.eval(ctx)?.cast::<EcoString>().at(self.path.span())?; let path = self.path.eval(ctx)?.cast::<Str>().at(self.path.span())?;
let file = ctx.import(&path, self.path.span())?; let file = ctx.import(&path, self.path.span())?;
let module = &ctx.modules[&file]; let module = &ctx.modules[&file];

View File

@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::iter; use std::iter;
use std::rc::Rc; use std::rc::Rc;
use super::{EvalContext, FuncArgs, Function, Value}; use super::{Arguments, EvalContext, Function, Value};
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::util::EcoString; use crate::util::EcoString;
@ -91,7 +91,7 @@ impl Scope {
/// Define a constant function. /// Define a constant function.
pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F) pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F)
where where
F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value> + 'static, F: Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value> + 'static,
{ {
let name = name.into(); let name = name.into();
self.def_const(name.clone(), Function::new(Some(name), f)); self.def_const(name.clone(), Function::new(Some(name), f));

137
src/eval/str.rs Normal file
View File

@ -0,0 +1,137 @@
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display, Formatter, Write};
use std::ops::{Add, AddAssign, Deref};
use crate::diag::StrResult;
use crate::util::EcoString;
/// A string value with inline storage and clone-on-write semantics.
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Str {
string: EcoString,
}
impl Str {
/// Create a new, empty string.
pub fn new() -> Self {
Self::default()
}
/// Whether the string is empty.
pub fn is_empty(&self) -> bool {
self.string.is_empty()
}
/// The length of the string in bytes.
pub fn len(&self) -> i64 {
self.string.len() as i64
}
/// Borrow this as a string slice.
pub fn as_str(&self) -> &str {
self.string.as_str()
}
/// Return an iterator over the chars as strings.
pub fn iter(&self) -> impl Iterator<Item = Str> + '_ {
self.chars().map(Into::into)
}
/// Repeat this string `n` times.
pub fn repeat(&self, n: i64) -> StrResult<Self> {
let n = usize::try_from(n)
.ok()
.and_then(|n| self.string.len().checked_mul(n).map(|_| n))
.ok_or_else(|| format!("cannot repeat this string {} times", n))?;
Ok(self.string.repeat(n).into())
}
}
impl Display for Str {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_char('"')?;
for c in self.chars() {
match c {
'\\' => f.write_str(r"\\")?,
'"' => f.write_str(r#"\""#)?,
'\n' => f.write_str(r"\n")?,
'\r' => f.write_str(r"\r")?,
'\t' => f.write_str(r"\t")?,
_ => f.write_char(c)?,
}
}
f.write_char('"')
}
}
impl Debug for Str {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&self.string, f)
}
}
impl Deref for Str {
type Target = str;
fn deref(&self) -> &str {
self.string.deref()
}
}
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.string.push_str(rhs.as_str());
}
}
impl From<char> for Str {
fn from(c: char) -> Self {
Self { string: c.into() }
}
}
impl From<&str> for Str {
fn from(string: &str) -> Self {
Self { string: string.into() }
}
}
impl From<String> for Str {
fn from(string: String) -> Self {
Self { string: string.into() }
}
}
impl From<EcoString> for Str {
fn from(string: EcoString) -> Self {
Self { string }
}
}
impl From<&EcoString> for Str {
fn from(string: &EcoString) -> Self {
Self { string: string.clone() }
}
}
impl From<Str> for EcoString {
fn from(string: Str) -> Self {
string.string
}
}
impl From<&Str> for EcoString {
fn from(string: &Str) -> Self {
string.string.clone()
}
}

View File

@ -1,10 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use std::ops::{Add, AddAssign, Deref}; use std::ops::{Add, AddAssign, Deref};
use std::rc::Rc; use std::rc::Rc;
use super::Value; use super::{Str, Value};
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::exec::ExecContext; use crate::exec::ExecContext;
use crate::syntax::{Expr, SyntaxTree}; use crate::syntax::{Expr, SyntaxTree};
@ -40,21 +40,9 @@ impl Template {
} }
} }
impl From<TemplateTree> for Template { impl Display for Template {
fn from(tree: TemplateTree) -> Self { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Self::new(vec![TemplateNode::Tree(tree)]) f.pad("<template>")
}
}
impl From<TemplateFunc> for Template {
fn from(func: TemplateFunc) -> Self {
Self::new(vec![TemplateNode::Func(func)])
}
}
impl From<EcoString> for Template {
fn from(string: EcoString) -> Self {
Self::new(vec![TemplateNode::Str(string)])
} }
} }
@ -83,24 +71,42 @@ impl AddAssign for Template {
} }
} }
impl Add<EcoString> for Template { impl Add<Str> for Template {
type Output = Self; type Output = Self;
fn add(mut self, rhs: EcoString) -> Self::Output { fn add(mut self, rhs: Str) -> Self::Output {
Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs)); Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs.into()));
self self
} }
} }
impl Add<Template> for EcoString { impl Add<Template> for Str {
type Output = Template; type Output = Template;
fn add(self, mut rhs: Template) -> Self::Output { fn add(self, mut rhs: Template) -> Self::Output {
Rc::make_mut(&mut rhs.nodes).insert(0, TemplateNode::Str(self)); Rc::make_mut(&mut rhs.nodes).insert(0, TemplateNode::Str(self.into()));
rhs rhs
} }
} }
impl From<TemplateTree> for Template {
fn from(tree: TemplateTree) -> Self {
Self::new(vec![TemplateNode::Tree(tree)])
}
}
impl From<TemplateFunc> for Template {
fn from(func: TemplateFunc) -> Self {
Self::new(vec![TemplateNode::Func(func)])
}
}
impl From<Str> for Template {
fn from(string: Str) -> Self {
Self::new(vec![TemplateNode::Str(string.into())])
}
}
/// One node of a template. /// One node of a template.
/// ///
/// Evaluating a template expression creates only a single node. Adding multiple /// Evaluating a template expression creates only a single node. Adding multiple

View File

@ -3,7 +3,7 @@ use std::cmp::Ordering;
use std::fmt::{self, Debug, Display, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use std::rc::Rc; use std::rc::Rc;
use super::{ops, Array, Dict, FuncArgs, Function, Template, TemplateFunc}; use super::{ops, Array, Dict, Function, Str, Template, TemplateFunc};
use crate::color::{Color, RgbaColor}; use crate::color::{Color, RgbaColor};
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::exec::ExecContext; use crate::exec::ExecContext;
@ -37,7 +37,7 @@ pub enum Value {
/// A color value: `#f79143ff`. /// A color value: `#f79143ff`.
Color(Color), Color(Color),
/// A string: `"string"`. /// A string: `"string"`.
Str(EcoString), Str(Str),
/// An array of values: `(1, "hi", 12cm)`. /// An array of values: `(1, "hi", 12cm)`.
Array(Array), Array(Array),
/// A dictionary value: `(color: #f79143, pattern: dashed)`. /// A dictionary value: `(color: #f79143, pattern: dashed)`.
@ -48,8 +48,6 @@ pub enum Value {
Func(Function), Func(Function),
/// A dynamic value. /// A dynamic value.
Dyn(Dynamic), Dyn(Dynamic),
/// Captured arguments to a function.
Args(Rc<FuncArgs>),
} }
impl Value { impl Value {
@ -75,12 +73,11 @@ impl Value {
Self::Linear(_) => Linear::TYPE_NAME, Self::Linear(_) => Linear::TYPE_NAME,
Self::Fractional(_) => Fractional::TYPE_NAME, Self::Fractional(_) => Fractional::TYPE_NAME,
Self::Color(_) => Color::TYPE_NAME, Self::Color(_) => Color::TYPE_NAME,
Self::Str(_) => EcoString::TYPE_NAME, Self::Str(_) => Str::TYPE_NAME,
Self::Array(_) => Array::TYPE_NAME, Self::Array(_) => Array::TYPE_NAME,
Self::Dict(_) => Dict::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME,
Self::Template(_) => Template::TYPE_NAME, Self::Template(_) => Template::TYPE_NAME,
Self::Func(_) => Function::TYPE_NAME, Self::Func(_) => Function::TYPE_NAME,
Self::Args(_) => Rc::<FuncArgs>::TYPE_NAME,
Self::Dyn(v) => v.type_name(), Self::Dyn(v) => v.type_name(),
} }
} }
@ -102,6 +99,48 @@ impl Value {
} }
} }
impl Default for Value {
fn default() -> Self {
Value::None
}
}
impl Display 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) => Display::fmt(v, f),
Self::Int(v) => Display::fmt(v, f),
Self::Float(v) => Display::fmt(v, f),
Self::Length(v) => Display::fmt(v, f),
Self::Angle(v) => Display::fmt(v, f),
Self::Relative(v) => Display::fmt(v, f),
Self::Linear(v) => Display::fmt(v, f),
Self::Fractional(v) => Display::fmt(v, f),
Self::Color(v) => Display::fmt(v, f),
Self::Str(v) => Display::fmt(v, f),
Self::Array(v) => Display::fmt(v, f),
Self::Dict(v) => Display::fmt(v, f),
Self::Template(v) => Display::fmt(v, f),
Self::Func(v) => Display::fmt(v, f),
Self::Dyn(v) => Display::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)
}
}
impl From<i32> for Value { impl From<i32> for Value {
fn from(v: i32) -> Self { fn from(v: i32) -> Self {
Self::Int(v as i64) Self::Int(v as i64)
@ -126,6 +165,12 @@ impl From<String> for Value {
} }
} }
impl From<EcoString> for Value {
fn from(v: EcoString) -> Self {
Self::Str(v.into())
}
}
impl From<RgbaColor> for Value { impl From<RgbaColor> for Value {
fn from(v: RgbaColor) -> Self { fn from(v: RgbaColor) -> Self {
Self::Color(Color::Rgba(v)) Self::Color(Color::Rgba(v))
@ -137,25 +182,6 @@ impl From<Dynamic> for Value {
Self::Dyn(v) Self::Dyn(v)
} }
} }
impl Default for Value {
fn default() -> Self {
Value::None
}
}
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)
}
}
/// A dynamic value. /// A dynamic value.
#[derive(Clone)] #[derive(Clone)]
pub struct Dynamic(Rc<dyn Bounds>); pub struct Dynamic(Rc<dyn Bounds>);
@ -193,7 +219,7 @@ impl Display for Dynamic {
impl Debug for Dynamic { impl Debug for Dynamic {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_tuple("ValueAny").field(&self.0).finish() Debug::fmt(&self.0, f)
} }
} }
@ -332,7 +358,7 @@ macro_rules! dynamic {
} }
castable! { castable! {
$type: Self::TYPE_NAME, $type: <Self as $crate::eval::Type>::TYPE_NAME,
$($tts)* $($tts)*
@this: Self => this.clone(), @this: Self => this.clone(),
} }
@ -379,16 +405,62 @@ macro_rules! castable {
primitive! { bool: "boolean", Bool } primitive! { bool: "boolean", Bool }
primitive! { i64: "integer", Int } primitive! { i64: "integer", Int }
primitive! { f64: "float", Float, Int(v) => v as f64 }
primitive! { Length: "length", Length } primitive! { Length: "length", Length }
primitive! { Angle: "angle", Angle } primitive! { Angle: "angle", Angle }
primitive! { Relative: "relative", Relative } primitive! { Relative: "relative", Relative }
primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() } primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() }
primitive! { Fractional: "fractional", Fractional } primitive! { Fractional: "fractional", Fractional }
primitive! { Color: "color", Color } primitive! { Color: "color", Color }
primitive! { EcoString: "string", Str } primitive! { Str: "string", Str }
primitive! { Array: "array", Array } primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict } primitive! { Dict: "dictionary", Dict }
primitive! { Template: "template", Template } primitive! { Template: "template", Template }
primitive! { Function: "function", Func } primitive! { Function: "function", Func }
primitive! { Rc<FuncArgs>: "arguments", Args }
primitive! { f64: "float", Float, Int(v) => v as f64 } #[cfg(test)]
mod tests {
use super::*;
#[track_caller]
fn test(value: impl Into<Value>, exp: &str) {
assert_eq!(value.into().to_string(), exp);
}
#[test]
fn test_value_to_string() {
// Primitives.
test(Value::None, "none");
test(false, "false");
test(12i64, "12");
test(3.14, "3.14");
test(Length::pt(5.5), "5.5pt");
test(Angle::deg(90.0), "90deg");
test(Relative::one() / 2.0, "50%");
test(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
test(Fractional::one() * 7.55, "7.55fr");
test(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#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], "(one: 1, two: false)");
// Functions.
test(Function::new(None, |_, _| Ok(Value::None)), "<function>");
test(
Function::new(Some("nil".into()), |_, _| Ok(Value::None)),
"<function nil>",
);
// Dynamics.
test(Dynamic::new(1), "1");
}
}

View File

@ -11,7 +11,6 @@ use std::fmt::Write;
use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value}; use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value};
use crate::geom::Gen; use crate::geom::Gen;
use crate::layout::{LayoutTree, StackChild, StackNode}; use crate::layout::{LayoutTree, StackChild, StackNode};
use crate::pretty::pretty;
use crate::syntax::*; use crate::syntax::*;
use crate::util::EcoString; use crate::util::EcoString;
use crate::Context; use crate::Context;
@ -133,13 +132,13 @@ impl Exec for Value {
fn exec(&self, ctx: &mut ExecContext) { fn exec(&self, ctx: &mut ExecContext) {
match self { match self {
Value::None => {} Value::None => {}
Value::Int(v) => ctx.push_text(pretty(v)), Value::Int(v) => ctx.push_text(v.to_string()),
Value::Float(v) => ctx.push_text(pretty(v)), Value::Float(v) => ctx.push_text(v.to_string()),
Value::Str(v) => ctx.push_text(v), Value::Str(v) => ctx.push_text(v),
Value::Template(v) => v.exec(ctx), Value::Template(v) => v.exec(ctx),
// For values which can't be shown "naturally", we print the // For values which can't be shown "naturally", we print the
// representation in monospace. // representation in monospace.
other => ctx.push_monospace_text(pretty(other)), other => ctx.push_monospace_text(other.to_string()),
} }
} }
} }

View File

@ -383,7 +383,7 @@ impl<'a> GridLayouter<'a> {
let frames = self.layout_multi_row(ctx, first, &rest, y); let frames = self.layout_multi_row(ctx, first, &rest, y);
let len = frames.len(); let len = frames.len();
for (i, frame) in frames.into_iter().enumerate() { for (i, frame) in frames.into_iter().enumerate() {
if i + 1 != len { if i + 1 < len {
self.constraints.exact.set(self.main, Some(self.full)); self.constraints.exact.set(self.main, Some(self.full));
} }
self.push_row(ctx, frame); self.push_row(ctx, frame);

View File

@ -111,7 +111,7 @@ impl<'a> StackLayouter<'a> {
let nodes = node.layout(ctx, &self.regions); let nodes = node.layout(ctx, &self.regions);
let len = nodes.len(); let len = nodes.len();
for (i, frame) in nodes.into_iter().enumerate() { for (i, frame) in nodes.into_iter().enumerate() {
if i + 1 != len { if i + 1 < len {
self.constraints.exact = self.full.to_spec().map(Some); self.constraints.exact = self.full.to_spec().map(Some);
} }
self.push_frame(frame.item, aligns); self.push_frame(frame.item, aligns);

View File

@ -43,7 +43,6 @@ pub mod library;
pub mod loading; pub mod loading;
pub mod paper; pub mod paper;
pub mod parse; pub mod parse;
pub mod pretty;
pub mod source; pub mod source;
pub mod syntax; pub mod syntax;
pub mod util; pub mod util;

View File

@ -10,8 +10,8 @@ use crate::layout::{
}; };
/// `image`: An image. /// `image`: An image.
pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn image(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let path = args.expect::<Spanned<EcoString>>("path to image file")?; let path = args.expect::<Spanned<Str>>("path to image file")?;
let width = args.named("width")?; let width = args.named("width")?;
let height = args.named("height")?; let height = args.named("height")?;
@ -29,7 +29,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `rect`: A rectangle with optional content. /// `rect`: A rectangle with optional content.
pub fn rect(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn rect(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let width = args.named("width")?; let width = args.named("width")?;
let height = args.named("height")?; let height = args.named("height")?;
let fill = args.named("fill")?; let fill = args.named("fill")?;
@ -38,7 +38,7 @@ pub fn rect(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `square`: A square with optional content. /// `square`: A square with optional content.
pub fn square(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn square(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let length = args.named::<Length>("length")?.map(Linear::from); let length = args.named::<Length>("length")?.map(Linear::from);
let width = match length { let width = match length {
Some(length) => Some(length), Some(length) => Some(length),
@ -80,7 +80,7 @@ fn rect_impl(
} }
/// `ellipse`: An ellipse with optional content. /// `ellipse`: An ellipse with optional content.
pub fn ellipse(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn ellipse(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let width = args.named("width")?; let width = args.named("width")?;
let height = args.named("height")?; let height = args.named("height")?;
let fill = args.named("fill")?; let fill = args.named("fill")?;
@ -89,7 +89,7 @@ pub fn ellipse(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `circle`: A circle with optional content. /// `circle`: A circle with optional content.
pub fn circle(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn circle(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let diameter = args.named("radius")?.map(|r: Length| 2.0 * Linear::from(r)); let diameter = args.named("radius")?.map(|r: Length| 2.0 * Linear::from(r));
let width = match diameter { let width = match diameter {
None => args.named("width")?, None => args.named("width")?,

View File

@ -3,8 +3,8 @@ use crate::layout::{FixedNode, GridNode, PadNode, StackChild, StackNode, TrackSi
use crate::paper::{Paper, PaperClass}; use crate::paper::{Paper, PaperClass};
/// `page`: Configure pages. /// `page`: Configure pages.
pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn page(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let paper = match args.eat::<Spanned<EcoString>>() { let paper = match args.eat::<Spanned<Str>>() {
Some(name) => match Paper::from_name(&name.v) { Some(name) => match Paper::from_name(&name.v) {
None => bail!(name.span, "invalid paper name"), None => bail!(name.span, "invalid paper name"),
paper => paper, paper => paper,
@ -74,21 +74,21 @@ pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `pagebreak`: Start a new page. /// `pagebreak`: Start a new page.
pub fn pagebreak(_: &mut EvalContext, _: &mut FuncArgs) -> TypResult<Value> { pub fn pagebreak(_: &mut EvalContext, _: &mut Arguments) -> TypResult<Value> {
Ok(Value::template(move |ctx| ctx.pagebreak(true, true))) Ok(Value::template(move |ctx| ctx.pagebreak(true, true)))
} }
/// `h`: Horizontal spacing. /// `h`: Horizontal spacing.
pub fn h(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn h(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
spacing_impl(args, GenAxis::Cross) spacing_impl(args, GenAxis::Cross)
} }
/// `v`: Vertical spacing. /// `v`: Vertical spacing.
pub fn v(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn v(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
spacing_impl(args, GenAxis::Main) spacing_impl(args, GenAxis::Main)
} }
fn spacing_impl(args: &mut FuncArgs, axis: GenAxis) -> TypResult<Value> { fn spacing_impl(args: &mut Arguments, axis: GenAxis) -> TypResult<Value> {
let spacing = args.expect::<Linear>("spacing")?; let spacing = args.expect::<Linear>("spacing")?;
Ok(Value::template(move |ctx| { Ok(Value::template(move |ctx| {
// TODO: Should this really always be font-size relative? // TODO: Should this really always be font-size relative?
@ -98,7 +98,7 @@ fn spacing_impl(args: &mut FuncArgs, axis: GenAxis) -> TypResult<Value> {
} }
/// `align`: Configure the alignment along the layouting axes. /// `align`: Configure the alignment along the layouting axes.
pub fn align(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn align(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let mut horizontal = args.named("horizontal")?; let mut horizontal = args.named("horizontal")?;
let mut vertical = args.named("vertical")?; let mut vertical = args.named("vertical")?;
let first = args.eat::<Align>(); let first = args.eat::<Align>();
@ -132,7 +132,7 @@ pub fn align(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `box`: Place content in a rectangular box. /// `box`: Place content in a rectangular box.
pub fn boxed(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn boxed(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let width = args.named("width")?; let width = args.named("width")?;
let height = args.named("height")?; let height = args.named("height")?;
let body = args.eat().unwrap_or_default(); let body = args.eat().unwrap_or_default();
@ -143,7 +143,7 @@ pub fn boxed(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `block`: Place content in a block. /// `block`: Place content in a block.
pub fn block(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn block(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let body = args.expect("body")?; let body = args.expect("body")?;
Ok(Value::template(move |ctx| { Ok(Value::template(move |ctx| {
let block = ctx.exec_template_stack(&body); let block = ctx.exec_template_stack(&body);
@ -152,7 +152,7 @@ pub fn block(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `pad`: Pad content at the sides. /// `pad`: Pad content at the sides.
pub fn pad(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn pad(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let all = args.eat(); let all = args.eat();
let left = args.named("left")?; let left = args.named("left")?;
let top = args.named("top")?; let top = args.named("top")?;
@ -174,7 +174,7 @@ pub fn pad(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `stack`: Stack children along an axis. /// `stack`: Stack children along an axis.
pub fn stack(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn stack(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let dir = args.named("dir")?; let dir = args.named("dir")?;
let children: Vec<_> = args.all().collect(); let children: Vec<_> = args.all().collect();
@ -200,7 +200,7 @@ pub fn stack(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `grid`: Arrange children into a grid. /// `grid`: Arrange children into a grid.
pub fn grid(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn grid(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let columns = args.named("columns")?.unwrap_or_default(); let columns = args.named("columns")?.unwrap_or_default();
let rows = args.named("rows")?.unwrap_or_default(); let rows = args.named("rows")?.unwrap_or_default();

View File

@ -18,12 +18,11 @@ use std::rc::Rc;
use crate::color::{Color, RgbaColor}; use crate::color::{Color, RgbaColor};
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::eval::{EvalContext, FuncArgs, Scope, Template, Type, Value}; use crate::eval::{Arguments, EvalContext, Scope, Str, Template, Value};
use crate::exec::Exec; use crate::exec::Exec;
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
use crate::geom::*; use crate::geom::*;
use crate::syntax::Spanned; use crate::syntax::Spanned;
use crate::util::EcoString;
/// Construct a scope containing all standard library definitions. /// Construct a scope containing all standard library definitions.
pub fn new() -> Scope { pub fn new() -> Scope {

View File

@ -4,7 +4,7 @@ use crate::layout::Paint;
use super::*; use super::*;
/// `font`: Configure the font. /// `font`: Configure the font.
pub fn font(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn font(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let size = args.named::<Linear>("size")?.or_else(|| args.eat()); let size = args.named::<Linear>("size")?.or_else(|| args.eat());
let style = args.named("style")?; let style = args.named("style")?;
let weight = args.named("weight")?; let weight = args.named("weight")?;
@ -98,13 +98,13 @@ castable! {
values values
.into_iter() .into_iter()
.filter_map(|v| v.cast().ok()) .filter_map(|v| v.cast().ok())
.map(|string: EcoString| string.to_lowercase()) .map(|string: Str| string.to_lowercase())
.collect() .collect()
)), )),
} }
/// `par`: Configure paragraphs. /// `par`: Configure paragraphs.
pub fn par(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn par(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let par_spacing = args.named("spacing")?; let par_spacing = args.named("spacing")?;
let line_spacing = args.named("leading")?; let line_spacing = args.named("leading")?;
let body = args.expect::<Template>("body")?; let body = args.expect::<Template>("body")?;
@ -126,8 +126,8 @@ pub fn par(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `lang`: Configure the language. /// `lang`: Configure the language.
pub fn lang(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn lang(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let iso = args.eat::<EcoString>(); let iso = args.eat::<Str>();
let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? { let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? {
if dir.v.axis() == SpecAxis::Horizontal { if dir.v.axis() == SpecAxis::Horizontal {
Some(dir.v) Some(dir.v)
@ -160,22 +160,22 @@ fn lang_dir(iso: &str) -> Dir {
} }
/// `strike`: Enable striken-through text. /// `strike`: Enable striken-through text.
pub fn strike(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn strike(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
line_impl(args, |font| &mut font.strikethrough) line_impl(args, |font| &mut font.strikethrough)
} }
/// `underline`: Enable underlined text. /// `underline`: Enable underlined text.
pub fn underline(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn underline(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
line_impl(args, |font| &mut font.underline) line_impl(args, |font| &mut font.underline)
} }
/// `overline`: Add an overline above text. /// `overline`: Add an overline above text.
pub fn overline(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn overline(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
line_impl(args, |font| &mut font.overline) line_impl(args, |font| &mut font.overline)
} }
fn line_impl( fn line_impl(
args: &mut FuncArgs, args: &mut Arguments,
substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>, substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
) -> TypResult<Value> { ) -> TypResult<Value> {
let stroke = args.named("stroke")?.or_else(|| args.eat()); let stroke = args.named("stroke")?.or_else(|| args.eat());

View File

@ -2,37 +2,34 @@ use std::cmp::Ordering;
use std::str::FromStr; use std::str::FromStr;
use crate::color::{Color, RgbaColor}; use crate::color::{Color, RgbaColor};
use crate::pretty::pretty;
use super::*; use super::*;
/// `type`: The name of a value's type. /// `type`: The name of a value's type.
pub fn type_(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn type_(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let value = args.expect::<Value>("value")?; Ok(args.expect::<Value>("value")?.type_name().into())
Ok(value.type_name().into())
} }
/// `repr`: The string representation of a value. /// `repr`: The string representation of a value.
pub fn repr(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn repr(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let value = args.expect::<Value>("value")?; Ok(args.expect::<Value>("value")?.to_string().into())
Ok(pretty(&value).into())
} }
/// `len`: The length of a string, an array or a dictionary. /// `len`: The length of a string, an array or a dictionary.
pub fn len(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn len(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let Spanned { v, span } = args.expect("collection")?; let Spanned { v, span } = args.expect("collection")?;
Ok(match v { Ok(Value::Int(match v {
Value::Str(v) => Value::Int(v.len() as i64), Value::Str(v) => v.len(),
Value::Array(v) => Value::Int(v.len()), Value::Array(v) => v.len(),
Value::Dict(v) => Value::Int(v.len()), Value::Dict(v) => v.len(),
_ => bail!(span, "expected string, array or dictionary"), _ => bail!(span, "expected string, array or dictionary"),
}) }))
} }
/// `rgb`: Create an RGB(A) color. /// `rgb`: Create an RGB(A) color.
pub fn rgb(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn rgb(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
Ok(Value::Color(Color::Rgba( Ok(Value::Color(Color::Rgba(
if let Some(string) = args.eat::<Spanned<EcoString>>() { if let Some(string) = args.eat::<Spanned<Str>>() {
match RgbaColor::from_str(&string.v) { match RgbaColor::from_str(&string.v) {
Ok(color) => color, Ok(color) => color,
Err(_) => bail!(string.span, "invalid color"), Err(_) => bail!(string.span, "invalid color"),
@ -49,35 +46,32 @@ pub fn rgb(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
} }
/// `min`: The minimum of a sequence of values. /// `min`: The minimum of a sequence of values.
pub fn min(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn min(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
minmax(args, Ordering::Less) minmax(args, Ordering::Less)
} }
/// `max`: The maximum of a sequence of values. /// `max`: The maximum of a sequence of values.
pub fn max(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> { pub fn max(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
minmax(args, Ordering::Greater) minmax(args, Ordering::Greater)
} }
/// Find the minimum or maximum of a sequence of values. /// Find the minimum or maximum of a sequence of values.
fn minmax(args: &mut FuncArgs, goal: Ordering) -> TypResult<Value> { fn minmax(args: &mut Arguments, goal: Ordering) -> TypResult<Value> {
let span = args.span;
let mut extremum = args.expect::<Value>("value")?; let mut extremum = args.expect::<Value>("value")?;
for value in args.all::<Value>() { for Spanned { v, span } in args.all::<Spanned<Value>>() {
match value.partial_cmp(&extremum) { match v.partial_cmp(&extremum) {
Some(ordering) => { Some(ordering) => {
if ordering == goal { if ordering == goal {
extremum = value; extremum = v;
} }
} }
None => bail!( None => bail!(
span, span,
"cannot compare {} with {}", "cannot compare {} with {}",
extremum.type_name(), extremum.type_name(),
value.type_name(), v.type_name(),
), ),
} }
} }
Ok(extremum) Ok(extremum)
} }

View File

@ -29,12 +29,12 @@ pub fn resolve_string(string: &str) -> EcoString {
out.push(c); out.push(c);
} else { } else {
// TODO: Feedback that unicode escape sequence is wrong. // TODO: Feedback that unicode escape sequence is wrong.
out += s.eaten_from(start); out.push_str(s.eaten_from(start));
} }
} }
// TODO: Feedback about invalid escape sequence. // TODO: Feedback about invalid escape sequence.
_ => out += s.eaten_from(start), _ => out.push_str(s.eaten_from(start)),
} }
} }
@ -138,7 +138,7 @@ mod tests {
fn test_resolve_strings() { fn test_resolve_strings() {
#[track_caller] #[track_caller]
fn test(string: &str, expected: &str) { fn test(string: &str, expected: &str) {
assert_eq!(resolve_string(string), expected.to_string()); assert_eq!(resolve_string(string), expected);
} }
test(r#"hello world"#, "hello world"); test(r#"hello world"#, "hello world");

View File

@ -606,20 +606,20 @@ mod tests {
}}; }};
(@$mode:ident: $src:expr => $($token:expr),*) => {{ (@$mode:ident: $src:expr => $($token:expr),*) => {{
let src = $src; let src = $src;
let exp = vec![$($token),*];
let found = Tokens::new(&src, $mode).collect::<Vec<_>>(); let found = Tokens::new(&src, $mode).collect::<Vec<_>>();
check(&src, exp, found); let expected = vec![$($token),*];
check(&src, found, expected);
}}; }};
} }
#[track_caller] #[track_caller]
fn check<T>(src: &str, exp: T, found: T) fn check<T>(src: &str, found: T, expected: T)
where where
T: Debug + PartialEq, T: Debug + PartialEq,
{ {
if exp != found { if found != expected {
println!("source: {:?}", src); println!("source: {:?}", src);
println!("expected: {:#?}", exp); println!("expected: {:#?}", expected);
println!("found: {:#?}", found); println!("found: {:#?}", found);
panic!("test failed"); panic!("test failed");
} }

View File

@ -23,14 +23,11 @@ pub struct Ident {
impl Ident { impl Ident {
/// Create a new identifier from a string checking that it is a valid. /// Create a new identifier from a string checking that it is a valid.
pub fn new( pub fn new(
string: impl AsRef<str> + Into<String>, string: impl AsRef<str> + Into<EcoString>,
span: impl Into<Span>, span: impl Into<Span>,
) -> Option<Self> { ) -> Option<Self> {
if is_ident(string.as_ref()) { if is_ident(string.as_ref()) {
Some(Self { Some(Self { span: span.into(), string: string.into() })
span: span.into(),
string: EcoString::from_str(string),
})
} else { } else {
None None
} }

View File

@ -3,6 +3,7 @@
mod expr; mod expr;
mod ident; mod ident;
mod node; mod node;
mod pretty;
mod span; mod span;
mod token; mod token;
pub mod visit; pub mod visit;
@ -10,6 +11,7 @@ pub mod visit;
pub use expr::*; pub use expr::*;
pub use ident::*; pub use ident::*;
pub use node::*; pub use node::*;
pub use pretty::*;
pub use span::*; pub use span::*;
pub use token::*; pub use token::*;

View File

@ -2,10 +2,7 @@
use std::fmt::{self, Arguments, Write}; use std::fmt::{self, Arguments, Write};
use crate::color::{Color, RgbaColor}; use super::*;
use crate::eval::*;
use crate::geom::{Angle, Fractional, Length, Linear, Relative};
use crate::syntax::*;
/// Pretty print an item and return the resulting string. /// Pretty print an item and return the resulting string.
pub fn pretty<T>(item: &T) -> String pub fn pretty<T>(item: &T) -> String
@ -23,7 +20,7 @@ pub trait Pretty {
fn pretty(&self, p: &mut Printer); fn pretty(&self, p: &mut Printer);
} }
/// A buffer into which items are printed. /// A buffer into which items can be pretty printed.
pub struct Printer { pub struct Printer {
buf: String, buf: String,
} }
@ -223,14 +220,14 @@ impl Pretty for Lit {
match self { match self {
Self::None(_) => p.push_str("none"), Self::None(_) => p.push_str("none"),
Self::Auto(_) => p.push_str("auto"), Self::Auto(_) => p.push_str("auto"),
Self::Bool(_, v) => v.pretty(p), Self::Bool(_, v) => write!(p, "{}", v).unwrap(),
Self::Int(_, v) => v.pretty(p), Self::Int(_, v) => write!(p, "{}", v).unwrap(),
Self::Float(_, v) => v.pretty(p), Self::Float(_, v) => write!(p, "{}", v).unwrap(),
Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(), Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(), Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Percent(_, v) => write!(p, "{}%", v).unwrap(), Self::Percent(_, v) => write!(p, "{}%", v).unwrap(),
Self::Fractional(_, v) => write!(p, "{}fr", v).unwrap(), Self::Fractional(_, v) => write!(p, "{}fr", v).unwrap(),
Self::Str(_, v) => v.pretty(p), Self::Str(_, v) => write!(p, "{:?}", v).unwrap(),
} }
} }
} }
@ -493,134 +490,6 @@ impl Pretty for Ident {
} }
} }
impl Pretty for Value {
fn pretty(&self, p: &mut Printer) {
match self {
Self::None => p.push_str("none"),
Self::Auto => p.push_str("auto"),
Self::Bool(v) => v.pretty(p),
Self::Int(v) => v.pretty(p),
Self::Float(v) => v.pretty(p),
Self::Length(v) => v.pretty(p),
Self::Angle(v) => v.pretty(p),
Self::Relative(v) => v.pretty(p),
Self::Linear(v) => v.pretty(p),
Self::Fractional(v) => v.pretty(p),
Self::Color(v) => v.pretty(p),
Self::Str(v) => v.pretty(p),
Self::Array(v) => v.pretty(p),
Self::Dict(v) => v.pretty(p),
Self::Template(v) => v.pretty(p),
Self::Func(v) => v.pretty(p),
Self::Args(v) => v.pretty(p),
Self::Dyn(v) => v.pretty(p),
}
}
}
impl Pretty for Array {
fn pretty(&self, p: &mut Printer) {
p.push('(');
p.join(self, ", ", |item, p| item.pretty(p));
if self.len() == 1 {
p.push(',');
}
p.push(')');
}
}
impl Pretty for Dict {
fn pretty(&self, p: &mut Printer) {
p.push('(');
if self.is_empty() {
p.push(':');
} else {
p.join(self, ", ", |(key, value), p| {
p.push_str(key);
p.push_str(": ");
value.pretty(p);
});
}
p.push(')');
}
}
impl Pretty for Template {
fn pretty(&self, p: &mut Printer) {
p.push_str("<template>");
}
}
impl Pretty for Function {
fn pretty(&self, p: &mut Printer) {
p.push_str("<function");
if let Some(name) = self.name() {
p.push(' ');
p.push_str(name);
}
p.push('>');
}
}
impl Pretty for FuncArgs {
fn pretty(&self, p: &mut Printer) {
p.push('(');
p.join(&self.items, ", ", |item, p| item.pretty(p));
p.push(')');
}
}
impl Pretty for FuncArg {
fn pretty(&self, p: &mut Printer) {
if let Some(name) = &self.name {
p.push_str(&name);
p.push_str(": ");
}
self.value.v.pretty(p);
}
}
impl Pretty for str {
fn pretty(&self, p: &mut Printer) {
p.push('"');
for c in self.chars() {
match c {
'\\' => p.push_str(r"\\"),
'"' => p.push_str(r#"\""#),
'\n' => p.push_str(r"\n"),
'\r' => p.push_str(r"\r"),
'\t' => p.push_str(r"\t"),
_ => p.push(c),
}
}
p.push('"');
}
}
macro_rules! pretty_display {
($($type:ty),* $(,)?) => {
$(impl Pretty for $type {
fn pretty(&self, p: &mut Printer) {
write!(p, "{}", self).unwrap();
}
})*
};
}
pretty_display! {
i64,
f64,
bool,
Length,
Angle,
Relative,
Linear,
Fractional,
RgbaColor,
Color,
Dynamic,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -633,23 +502,18 @@ mod tests {
} }
#[track_caller] #[track_caller]
fn test_parse(src: &str, exp: &str) { fn test_parse(src: &str, expected: &str) {
let source = SourceFile::detached(src); let source = SourceFile::detached(src);
let ast = parse(&source).unwrap(); let ast = parse(&source).unwrap();
let found = pretty(&ast); let found = pretty(&ast);
if exp != found { if found != expected {
println!("tree: {:#?}", ast); println!("tree: {:#?}", ast);
println!("expected: {}", exp); println!("expected: {}", expected);
println!("found: {}", found); println!("found: {}", found);
panic!("test failed"); panic!("test failed");
} }
} }
#[track_caller]
fn test_value(value: impl Into<Value>, exp: &str) {
assert_eq!(pretty(&value.into()), exp);
}
#[test] #[test]
fn test_pretty_print_node() { fn test_pretty_print_node() {
// Basic text and markup. // Basic text and markup.
@ -746,41 +610,4 @@ mod tests {
roundtrip("#import * from \"file.typ\""); roundtrip("#import * from \"file.typ\"");
roundtrip("#include \"chapter1.typ\""); roundtrip("#include \"chapter1.typ\"");
} }
#[test]
fn test_pretty_print_value() {
// Primitives.
test_value(Value::None, "none");
test_value(false, "false");
test_value(12i64, "12");
test_value(3.14, "3.14");
test_value(Length::pt(5.5), "5.5pt");
test_value(Angle::deg(90.0), "90deg");
test_value(Relative::one() / 2.0, "50%");
test_value(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
test_value(Fractional::one() * 7.55, "7.55fr");
test_value(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
// Collections.
test_value("hello", r#""hello""#);
test_value("\n", r#""\n""#);
test_value("\\", r#""\\""#);
test_value("\"", r#""\"""#);
test_value(array![], "()");
test_value(array![Value::None], "(none,)");
test_value(array![1, 2], "(1, 2)");
test_value(dict![], "(:)");
test_value(dict!["one" => 1], "(one: 1)");
test_value(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
// Functions.
test_value(Function::new(None, |_, _| Ok(Value::None)), "<function>");
test_value(
Function::new(Some("nil".into()), |_, _| Ok(Value::None)),
"<function nil>",
);
// Dynamics.
test_value(Dynamic::new(1), "1");
}
} }

View File

@ -142,18 +142,6 @@ impl Pos {
} }
} }
impl From<u32> for Pos {
fn from(index: u32) -> Self {
Self(index)
}
}
impl From<usize> for Pos {
fn from(index: usize) -> Self {
Self(index as u32)
}
}
impl Debug for Pos { impl Debug for Pos {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&self.0, f) Debug::fmt(&self.0, f)
@ -171,6 +159,18 @@ where
} }
} }
impl From<u32> for Pos {
fn from(index: u32) -> Self {
Self(index)
}
}
impl From<usize> for Pos {
fn from(index: usize) -> Self {
Self(index as u32)
}
}
/// Convert a position or range into a span. /// Convert a position or range into a span.
pub trait IntoSpan { pub trait IntoSpan {
/// Convert into a span by providing the source id. /// Convert into a span by providing the source id.

View File

@ -1,14 +1,11 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display, Formatter, Write}; use std::fmt::{self, Debug, Display, Formatter, Write};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::ops::{Add, AddAssign, Deref}; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use crate::diag::StrResult; /// An economical string with inline storage and clone-on-write semantics.
/// An economical string with inline storage and clone-on-write value semantics.
#[derive(Clone)] #[derive(Clone)]
pub struct EcoString(Repr); pub struct EcoString(Repr);
@ -144,75 +141,25 @@ impl EcoString {
} }
/// Repeat this string `n` times. /// Repeat this string `n` times.
pub fn repeat(&self, n: i64) -> StrResult<Self> { pub fn repeat(&self, n: usize) -> Self {
let (n, new) = usize::try_from(n) if n == 0 {
.ok() return Self::new();
.and_then(|n| self.len().checked_mul(n).map(|new| (n, new))) }
.ok_or_else(|| format!("cannot repeat this string {} times", n))?;
if let Repr::Small { buf, len } = &self.0 { if let Repr::Small { buf, len } = &self.0 {
let prev = usize::from(*len); let prev = usize::from(*len);
let new = prev.saturating_mul(n);
if new <= LIMIT { if new <= LIMIT {
let src = &buf[.. prev]; let src = &buf[.. prev];
let mut buf = [0; LIMIT]; let mut buf = [0; LIMIT];
for i in 0 .. n { for i in 0 .. n {
buf[prev * i .. prev * (i + 1)].copy_from_slice(src); buf[prev * i .. prev * (i + 1)].copy_from_slice(src);
} }
return Ok(Self(Repr::Small { buf, len: new as u8 })); return Self(Repr::Small { buf, len: new as u8 });
} }
} }
Ok(self.as_str().repeat(n).into()) self.as_str().repeat(n).into()
}
}
impl From<&Self> for EcoString {
fn from(s: &Self) -> Self {
s.clone()
}
}
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(s)
}
}
impl From<String> for EcoString {
fn from(s: String) -> Self {
Self::from_str(s)
}
}
impl From<&String> for EcoString {
fn from(s: &String) -> Self {
Self::from_str(s)
}
}
impl From<EcoString> for String {
fn from(s: EcoString) -> Self {
match s.0 {
Repr::Small { .. } => s.as_str().to_owned(),
Repr::Large(rc) => match Rc::try_unwrap(rc) {
Ok(string) => string,
Err(rc) => (*rc).clone(),
},
}
}
}
impl From<&EcoString> for String {
fn from(s: &EcoString) -> Self {
s.as_str().to_owned()
} }
} }
@ -236,18 +183,6 @@ impl Deref for EcoString {
} }
} }
impl AsRef<str> for EcoString {
fn as_ref(&self) -> &str {
self
}
}
impl Borrow<str> for EcoString {
fn borrow(&self) -> &str {
self
}
}
impl Default for EcoString { impl Default for EcoString {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@ -286,12 +221,6 @@ impl PartialEq<&str> for EcoString {
} }
} }
impl PartialEq<String> for EcoString {
fn eq(&self, other: &String) -> bool {
self.as_str().eq(other.as_str())
}
}
impl Ord for EcoString { impl Ord for EcoString {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str()) self.as_str().cmp(other.as_str())
@ -304,35 +233,6 @@ impl PartialOrd for EcoString {
} }
} }
impl Add for EcoString {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
self + rhs.as_str()
}
}
impl AddAssign for EcoString {
fn add_assign(&mut self, rhs: EcoString) {
self.push_str(&rhs);
}
}
impl Add<&str> for EcoString {
type Output = Self;
fn add(mut self, rhs: &str) -> Self::Output {
self.push_str(rhs);
self
}
}
impl AddAssign<&str> for EcoString {
fn add_assign(&mut self, rhs: &str) {
self.push_str(rhs);
}
}
impl Hash for EcoString { impl Hash for EcoString {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state); self.as_str().hash(state);
@ -351,6 +251,62 @@ impl Write for EcoString {
} }
} }
impl AsRef<str> for EcoString {
fn as_ref(&self) -> &str {
self
}
}
impl Borrow<str> for EcoString {
fn borrow(&self) -> &str {
self
}
}
impl From<&Self> for EcoString {
fn from(s: &Self) -> Self {
s.clone()
}
}
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(s)
}
}
impl From<String> for EcoString {
fn from(s: String) -> Self {
Self::from_str(s)
}
}
impl From<EcoString> for String {
fn from(s: EcoString) -> Self {
match s.0 {
Repr::Small { .. } => s.as_str().to_owned(),
Repr::Large(rc) => match Rc::try_unwrap(rc) {
Ok(string) => string,
Err(rc) => (*rc).clone(),
},
}
}
}
impl From<&EcoString> for String {
fn from(s: &EcoString) -> Self {
s.as_str().to_owned()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -436,17 +392,13 @@ mod tests {
#[test] #[test]
fn test_str_repeat() { fn test_str_repeat() {
// Test with empty string. // Test with empty string.
assert_eq!(EcoString::new().repeat(0).unwrap(), ""); assert_eq!(EcoString::new().repeat(0), "");
assert_eq!(EcoString::new().repeat(100).unwrap(), ""); assert_eq!(EcoString::new().repeat(100), "");
// Test non-spilling and spilling case. // Test non-spilling and spilling case.
let v = EcoString::from("abc"); let v = EcoString::from("abc");
assert_eq!(v.repeat(0).unwrap(), ""); assert_eq!(v.repeat(0), "");
assert_eq!(v.repeat(3).unwrap(), "abcabcabc"); assert_eq!(v.repeat(3), "abcabcabc");
assert_eq!(v.repeat(5).unwrap(), "abcabcabcabcabc"); assert_eq!(v.repeat(5), "abcabcabcabcabc");
assert_eq!(
v.repeat(-1).unwrap_err(),
"cannot repeat this string -1 times",
);
} }
} }

View File

@ -13,5 +13,5 @@
#min() #min()
--- ---
// Error: 10-19 cannot compare integer with string // Error: 14-18 cannot compare integer with string
#test(min(1, "hi"), error) #test(min(1, "hi"), error)