From 6ae6d86b9c6fefe6c5379ac1b20ea90634c09c81 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 14 Aug 2021 15:24:59 +0200 Subject: [PATCH] Separate type for string values --- src/eval/array.rs | 39 ++++---- src/eval/dict.rs | 70 ++++++++------ src/eval/function.rs | 72 ++++++++++---- src/eval/mod.rs | 46 +++++---- src/eval/scope.rs | 4 +- src/eval/str.rs | 137 ++++++++++++++++++++++++++ src/eval/template.rs | 50 +++++----- src/eval/value.rs | 132 +++++++++++++++++++------ src/exec/mod.rs | 7 +- src/layout/grid.rs | 2 +- src/layout/stack.rs | 2 +- src/lib.rs | 1 - src/library/elements.rs | 12 +-- src/library/layout.rs | 24 ++--- src/library/mod.rs | 3 +- src/library/text.rs | 18 ++-- src/library/utility.rs | 44 ++++----- src/parse/resolve.rs | 6 +- src/parse/tokens.rs | 10 +- src/syntax/ident.rs | 7 +- src/syntax/mod.rs | 2 + src/{ => syntax}/pretty.rs | 191 ++----------------------------------- src/syntax/span.rs | 24 ++--- src/util/eco.rs | 188 ++++++++++++++---------------------- tests/typ/utility/math.typ | 2 +- 25 files changed, 565 insertions(+), 528 deletions(-) create mode 100644 src/eval/str.rs rename src/{ => syntax}/pretty.rs (74%) diff --git a/src/eval/array.rs b/src/eval/array.rs index 356aa0ca9..ec8f46d37 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -1,5 +1,5 @@ use std::convert::TryFrom; -use std::fmt::{self, Debug, Formatter}; +use std::fmt::{self, Debug, Display, Formatter, Write}; use std::iter::FromIterator; use std::ops::{Add, AddAssign}; use std::rc::Rc; @@ -19,8 +19,8 @@ macro_rules! array { }; } -/// A variably-typed array with clone-on-write value semantics. -#[derive(Clone, PartialEq)] +/// An array of values with clone-on-write value semantics. +#[derive(Default, Clone, PartialEq)] pub struct Array { vec: Rc>, } @@ -28,7 +28,7 @@ pub struct Array { impl Array { /// Create a new, empty array. pub fn new() -> Self { - Self { vec: Rc::new(vec![]) } + Self::default() } /// Create a new array from a vector of values. @@ -36,16 +36,9 @@ impl Array { 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. pub fn is_empty(&self) -> bool { - self.len() == 0 + self.vec.is_empty() } /// 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) } -impl Default for Array { - fn default() -> Self { - Self::new() - } -} - impl Debug for Array { fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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 { type Output = Self; diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 730392dc8..e71d0c6a0 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -1,13 +1,11 @@ use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter}; +use std::fmt::{self, Debug, Display, Formatter, Write}; use std::iter::FromIterator; use std::ops::{Add, AddAssign}; use std::rc::Rc; -use super::Value; +use super::{Str, Value}; use crate::diag::StrResult; -use crate::pretty::pretty; -use crate::util::EcoString; /// Create a new [`Dict`] from key-value pairs. #[allow(unused_macros)] @@ -15,15 +13,15 @@ macro_rules! dict { ($($key:expr => $value:expr),* $(,)?) => {{ #[allow(unused_mut)] 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) }}; } -/// A variably-typed dictionary with clone-on-write value semantics. -#[derive(Clone, PartialEq)] +/// A dictionary from strings to values with clone-on-write value semantics. +#[derive(Default, Clone, PartialEq)] pub struct Dict { - map: Rc>, + map: Rc>, } impl Dict { @@ -33,13 +31,13 @@ impl Dict { } /// Create a new dictionary from a mapping of strings to values. - pub fn from_map(map: BTreeMap) -> Self { + pub fn from_map(map: BTreeMap) -> Self { Self { map: Rc::new(map) } } /// Whether the dictionary is empty. pub fn is_empty(&self) -> bool { - self.len() == 0 + self.map.is_empty() } /// The number of pairs in the dictionary. @@ -48,21 +46,21 @@ impl Dict { } /// Borrow the value the given `key` maps to. - pub fn get(&self, key: &str) -> StrResult<&Value> { - self.map.get(key).ok_or_else(|| missing_key(key)) + pub fn get(&self, key: Str) -> StrResult<&Value> { + self.map.get(&key).ok_or_else(|| missing_key(&key)) } /// Mutably borrow the value the given `key` maps to. /// /// This inserts the key with [`None`](Value::None) as the value if not /// present so far. - pub fn get_mut(&mut self, key: EcoString) -> &mut Value { - Rc::make_mut(&mut self.map).entry(key).or_default() + pub fn get_mut(&mut self, key: Str) -> &mut Value { + Rc::make_mut(&mut self.map).entry(key.into()).or_default() } /// Insert a mapping from the given `key` to the given `value`. - pub fn insert(&mut self, key: EcoString, value: Value) { - Rc::make_mut(&mut self.map).insert(key, value); + pub fn insert(&mut self, key: Str, value: Value) { + Rc::make_mut(&mut self.map).insert(key.into(), value); } /// Clear the dictionary. @@ -75,20 +73,32 @@ impl Dict { } /// Iterate over pairs of references to the contained keys and values. - pub fn iter(&self) -> std::collections::btree_map::Iter { + pub fn iter(&self) -> std::collections::btree_map::Iter { self.map.iter() } } /// The missing key access error message. #[cold] -fn missing_key(key: &str) -> String { - format!("dictionary does not contain key: {}", pretty(key)) +fn missing_key(key: &Str) -> String { + format!("dictionary does not contain key: {}", key) } -impl Default for Dict { - fn default() -> Self { - Self::new() +impl Display for Dict { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + 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 { - fn from_iter>(iter: T) -> Self { +impl FromIterator<(Str, Value)> for Dict { + fn from_iter>(iter: T) -> Self { Dict { map: Rc::new(iter.into_iter().collect()) } } } -impl Extend<(EcoString, Value)> for Dict { - fn extend>(&mut self, iter: T) { +impl Extend<(Str, Value)> for Dict { + fn extend>(&mut self, iter: T) { Rc::make_mut(&mut self.map).extend(iter); } } impl IntoIterator for Dict { - type Item = (EcoString, Value); - type IntoIter = std::collections::btree_map::IntoIter; + type Item = (Str, Value); + type IntoIter = std::collections::btree_map::IntoIter; fn into_iter(self) -> Self::IntoIter { match Rc::try_unwrap(self.map) { @@ -141,8 +151,8 @@ impl IntoIterator for Dict { } impl<'a> IntoIterator for &'a Dict { - type Item = (&'a EcoString, &'a Value); - type IntoIter = std::collections::btree_map::Iter<'a, EcoString, Value>; + type Item = (&'a Str, &'a Value); + type IntoIter = std::collections::btree_map::Iter<'a, Str, Value>; fn into_iter(self) -> Self::IntoIter { self.iter() diff --git a/src/eval/function.rs b/src/eval/function.rs index 550db59df..f18af05eb 100644 --- a/src/eval/function.rs +++ b/src/eval/function.rs @@ -1,15 +1,17 @@ -use std::fmt::{self, Debug, Formatter}; +use std::fmt::{self, Debug, Display, Formatter, Write}; use std::ops::Deref; use std::rc::Rc; -use super::{Cast, EvalContext, Value}; +use super::{Cast, EvalContext, Str, Value}; use crate::diag::{At, TypResult}; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; /// An evaluatable function. #[derive(Clone)] -pub struct Function(Rc>); +pub struct Function { + repr: Rc>, +} /// The unsized representation behind the [`Rc`]. struct Repr { @@ -17,20 +19,20 @@ struct Repr { func: T, } -type Func = dyn Fn(&mut EvalContext, &mut FuncArgs) -> TypResult; +type Func = dyn Fn(&mut EvalContext, &mut Arguments) -> TypResult; impl Function { /// Create a new function from a rust closure. pub fn new(name: Option, func: F) -> Self where - F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult + 'static, + F: Fn(&mut EvalContext, &mut Arguments) -> TypResult + 'static, { - Self(Rc::new(Repr { name, func })) + Self { repr: Rc::new(Repr { name, func }) } } /// The name of the function. 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; 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("') } } impl Debug for Function { 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 { fn eq(&self, other: &Self) -> bool { // 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. #[derive(Debug, Clone, PartialEq)] -pub struct FuncArgs { +pub struct Arguments { /// The span of the whole argument list. pub span: Span, /// The positional and named arguments. - pub items: Vec, + pub items: Vec, } /// An argument to a function call: `12` or `draw: false`. #[derive(Debug, Clone, PartialEq)] -pub struct FuncArg { +pub struct Argument { /// The span of the whole argument. pub span: Span, /// The name of the argument (`None` for positional arguments). - pub name: Option, + pub name: Option, /// The value of the argument. pub value: Spanned, } -impl FuncArgs { +impl Arguments { /// Find and consume the first castable positional argument. pub fn eat(&mut self) -> Option where @@ -150,16 +163,14 @@ impl FuncArgs { } Ok(()) } -} -impl FuncArgs { /// Reinterpret these arguments as actually being an array index. pub fn into_index(self) -> TypResult { self.into_castable("index") } /// Reinterpret these arguments as actually being a dictionary key. - pub fn into_key(self) -> TypResult { + pub fn into_key(self) -> TypResult { self.into_castable("key") } @@ -170,11 +181,11 @@ impl FuncArgs { { let mut iter = self.items.into_iter(); 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 => { bail!(self.span, "missing {}", what); } - Some(FuncArg { name: Some(_), span, .. }) => { + Some(Argument { name: Some(_), span, .. }) => { bail!(span, "named pair is not allowed here"); } }; @@ -186,3 +197,24 @@ impl FuncArgs { 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", +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 9e6fad8fd..30b34798d 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -10,8 +10,10 @@ mod capture; mod function; mod ops; mod scope; +mod str; mod template; +pub use self::str::*; pub use array::*; pub use capture::*; pub use dict::*; @@ -35,7 +37,7 @@ use crate::parse::parse; use crate::source::{SourceId, SourceStore}; use crate::syntax::visit::Visit; use crate::syntax::*; -use crate::util::{EcoString, RefMutExt}; +use crate::util::RefMutExt; use crate::Context; /// 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::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)), 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.items .iter() - .map(|Named { name, expr }| Ok((name.string.clone(), expr.eval(ctx)?))) + .map(|Named { name, expr }| Ok(((&name.string).into(), expr.eval(ctx)?))) .collect() } } @@ -373,7 +375,7 @@ impl Eval for CallExpr { } 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) => { @@ -393,7 +395,7 @@ impl Eval for CallExpr { } impl Eval for CallArgs { - type Output = FuncArgs; + type Output = Arguments; fn eval(&self, ctx: &mut EvalContext) -> TypResult { let mut items = Vec::with_capacity(self.items.len()); @@ -402,43 +404,49 @@ impl Eval for CallArgs { let span = arg.span(); match arg { CallArg::Pos(expr) => { - items.push(FuncArg { + items.push(Argument { span, name: None, value: Spanned::new(expr.eval(ctx)?, expr.span()), }); } CallArg::Named(Named { name, expr }) => { - items.push(FuncArg { + items.push(Argument { span, - name: Some(name.string.clone()), + name: Some((&name.string).into()), value: Spanned::new(expr.eval(ctx)?, expr.span()), }); } CallArg::Spread(expr) => match expr.eval(ctx)? { - Value::Args(args) => { - items.extend(args.items.iter().cloned()); - } Value::Array(array) => { - items.extend(array.into_iter().map(|value| FuncArg { + items.extend(array.into_iter().map(|value| Argument { span, name: None, value: Spanned::new(value, span), })); } Value::Dict(dict) => { - items.extend(dict.into_iter().map(|(key, value)| FuncArg { + items.extend(dict.into_iter().map(|(key, value)| Argument { span, name: Some(key), 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::() { + 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. 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)?; @@ -599,7 +607,7 @@ impl Eval for ForExpr { let iter = self.iter.eval(ctx)?; match (&self.pattern, iter) { (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)) => { iter!(for (v => value) in array.into_iter()) @@ -627,7 +635,7 @@ impl Eval for ImportExpr { type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - let path = self.path.eval(ctx)?.cast::().at(self.path.span())?; + let path = self.path.eval(ctx)?.cast::().at(self.path.span())?; let file = ctx.import(&path, self.path.span())?; let module = &ctx.modules[&file]; @@ -657,7 +665,7 @@ impl Eval for IncludeExpr { type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - let path = self.path.eval(ctx)?.cast::().at(self.path.span())?; + let path = self.path.eval(ctx)?.cast::().at(self.path.span())?; let file = ctx.import(&path, self.path.span())?; let module = &ctx.modules[&file]; diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 2eb048fa3..2968ca205 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter}; use std::iter; use std::rc::Rc; -use super::{EvalContext, FuncArgs, Function, Value}; +use super::{Arguments, EvalContext, Function, Value}; use crate::diag::TypResult; use crate::util::EcoString; @@ -91,7 +91,7 @@ impl Scope { /// Define a constant function. pub fn def_func(&mut self, name: impl Into, f: F) where - F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult + 'static, + F: Fn(&mut EvalContext, &mut Arguments) -> TypResult + 'static, { let name = name.into(); self.def_const(name.clone(), Function::new(Some(name), f)); diff --git a/src/eval/str.rs b/src/eval/str.rs new file mode 100644 index 000000000..7f84f80f1 --- /dev/null +++ b/src/eval/str.rs @@ -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 + '_ { + self.chars().map(Into::into) + } + + /// Repeat this string `n` times. + pub fn repeat(&self, n: i64) -> StrResult { + 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 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 for Str { + fn from(string: String) -> Self { + Self { string: string.into() } + } +} + +impl From 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 for EcoString { + fn from(string: Str) -> Self { + string.string + } +} + +impl From<&Str> for EcoString { + fn from(string: &Str) -> Self { + string.string.clone() + } +} diff --git a/src/eval/template.rs b/src/eval/template.rs index 4e20b8f8f..594036afb 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; 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::rc::Rc; -use super::Value; +use super::{Str, Value}; use crate::diag::StrResult; use crate::exec::ExecContext; use crate::syntax::{Expr, SyntaxTree}; @@ -40,21 +40,9 @@ impl Template { } } -impl From for Template { - fn from(tree: TemplateTree) -> Self { - Self::new(vec![TemplateNode::Tree(tree)]) - } -} - -impl From for Template { - fn from(func: TemplateFunc) -> Self { - Self::new(vec![TemplateNode::Func(func)]) - } -} - -impl From for Template { - fn from(string: EcoString) -> Self { - Self::new(vec![TemplateNode::Str(string)]) +impl Display for Template { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("