diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-08-14 15:24:59 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-08-14 15:55:39 +0200 |
| commit | 6ae6d86b9c6fefe6c5379ac1b20ea90634c09c81 (patch) | |
| tree | 2504c3b46807be148b9efbadf9b23e57bb77b8f3 /src/eval | |
| parent | fcb4e451186067cdf6efe3c14cbfa7561b366a6c (diff) | |
Separate type for string values
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/array.rs | 37 | ||||
| -rw-r--r-- | src/eval/dict.rs | 70 | ||||
| -rw-r--r-- | src/eval/function.rs | 72 | ||||
| -rw-r--r-- | src/eval/mod.rs | 46 | ||||
| -rw-r--r-- | src/eval/scope.rs | 4 | ||||
| -rw-r--r-- | src/eval/str.rs | 137 | ||||
| -rw-r--r-- | src/eval/template.rs | 50 | ||||
| -rw-r--r-- | src/eval/value.rs | 132 |
8 files changed, 408 insertions, 140 deletions
diff --git a/src/eval/array.rs b/src/eval/array.rs index 356aa0ca..ec8f46d3 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<Vec<Value>>, } @@ -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,15 +99,25 @@ 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 Debug for Array { +impl Display for Array { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_list().entries(self.vec.iter()).finish() + 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(')') } } diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 730392dc..e71d0c6a 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<BTreeMap<EcoString, Value>>, + map: Rc<BTreeMap<Str, Value>>, } 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<EcoString, Value>) -> Self { + pub fn from_map(map: BTreeMap<Str, Value>) -> 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<EcoString, Value> { + pub fn iter(&self) -> std::collections::btree_map::Iter<Str, Value> { 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<T: IntoIterator<Item = (EcoString, Value)>>(iter: T) -> Self { +impl FromIterator<(Str, Value)> for Dict { + fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self { Dict { map: Rc::new(iter.into_iter().collect()) } } } -impl Extend<(EcoString, Value)> for Dict { - fn extend<T: IntoIterator<Item = (EcoString, Value)>>(&mut self, iter: T) { +impl Extend<(Str, Value)> for Dict { + fn extend<T: IntoIterator<Item = (Str, Value)>>(&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<EcoString, Value>; + type Item = (Str, Value); + type IntoIter = std::collections::btree_map::IntoIter<Str, Value>; 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 550db59d..f18af05e 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<Repr<Func>>); +pub struct Function { + repr: Rc<Repr<Func>>, +} /// The unsized representation behind the [`Rc`]. struct Repr<T: ?Sized> { @@ -17,20 +19,20 @@ struct Repr<T: ?Sized> { func: T, } -type Func = dyn Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value>; +type Func = dyn Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value>; impl Function { /// Create a new function from a rust closure. pub fn new<F>(name: Option<EcoString>, func: F) -> Self 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. 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("<function")?; + if let Some(name) = self.name() { + f.write_char(' ')?; + f.write_str(name)?; + } + f.write_char('>') } } 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<FuncArg>, + pub items: Vec<Argument>, } /// 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<EcoString>, + pub name: Option<Str>, /// The value of the argument. pub value: Spanned<Value>, } -impl FuncArgs { +impl Arguments { /// Find and consume the first castable positional argument. pub fn eat<T>(&mut self) -> Option<T> 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<i64> { self.into_castable("index") } /// 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") } @@ -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 9e6fad8f..30b34798 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::Output> { 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<Self::Output> { 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::<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. 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<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 module = &ctx.modules[&file]; @@ -657,7 +665,7 @@ impl Eval for IncludeExpr { type Output = Value; 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 module = &ctx.modules[&file]; diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 2eb048fa..2968ca20 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<F>(&mut self, name: impl Into<EcoString>, f: F) where - F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value> + 'static, + F: Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value> + '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 00000000..7f84f80f --- /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<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() + } +} diff --git a/src/eval/template.rs b/src/eval/template.rs index 4e20b8f8..594036af 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<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<EcoString> 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("<template>") } } @@ -83,24 +71,42 @@ impl AddAssign for Template { } } -impl Add<EcoString> for Template { +impl Add<Str> for Template { type Output = Self; - fn add(mut self, rhs: EcoString) -> Self::Output { - Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs)); + fn add(mut self, rhs: Str) -> Self::Output { + Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs.into())); self } } -impl Add<Template> for EcoString { +impl Add<Template> for Str { type Output = Template; 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 } } +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. /// /// Evaluating a template expression creates only a single node. Adding multiple diff --git a/src/eval/value.rs b/src/eval/value.rs index 958077da..62899cc1 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use std::fmt::{self, Debug, Display, Formatter}; 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::diag::StrResult; use crate::exec::ExecContext; @@ -37,7 +37,7 @@ pub enum Value { /// A color value: `#f79143ff`. Color(Color), /// A string: `"string"`. - Str(EcoString), + Str(Str), /// An array of values: `(1, "hi", 12cm)`. Array(Array), /// A dictionary value: `(color: #f79143, pattern: dashed)`. @@ -48,8 +48,6 @@ pub enum Value { Func(Function), /// A dynamic value. Dyn(Dynamic), - /// Captured arguments to a function. - Args(Rc<FuncArgs>), } impl Value { @@ -75,12 +73,11 @@ impl Value { Self::Linear(_) => Linear::TYPE_NAME, Self::Fractional(_) => Fractional::TYPE_NAME, Self::Color(_) => Color::TYPE_NAME, - Self::Str(_) => EcoString::TYPE_NAME, + Self::Str(_) => Str::TYPE_NAME, Self::Array(_) => Array::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME, Self::Template(_) => Template::TYPE_NAME, Self::Func(_) => Function::TYPE_NAME, - Self::Args(_) => Rc::<FuncArgs>::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 { fn from(v: i32) -> Self { 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 { fn from(v: RgbaColor) -> Self { Self::Color(Color::Rgba(v)) @@ -137,25 +182,6 @@ impl From<Dynamic> for Value { 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. #[derive(Clone)] pub struct Dynamic(Rc<dyn Bounds>); @@ -193,7 +219,7 @@ impl Display for Dynamic { impl Debug for Dynamic { 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! { - $type: Self::TYPE_NAME, + $type: <Self as $crate::eval::Type>::TYPE_NAME, $($tts)* @this: Self => this.clone(), } @@ -379,16 +405,62 @@ macro_rules! castable { primitive! { bool: "boolean", Bool } primitive! { i64: "integer", Int } +primitive! { f64: "float", Float, Int(v) => v as f64 } primitive! { Length: "length", Length } primitive! { Angle: "angle", Angle } primitive! { Relative: "relative", Relative } primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() } primitive! { Fractional: "fractional", Fractional } primitive! { Color: "color", Color } -primitive! { EcoString: "string", Str } +primitive! { Str: "string", Str } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Template: "template", Template } 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"); + } +} |
