summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/array.rs37
-rw-r--r--src/eval/dict.rs70
-rw-r--r--src/eval/function.rs72
-rw-r--r--src/eval/mod.rs46
-rw-r--r--src/eval/scope.rs4
-rw-r--r--src/eval/str.rs137
-rw-r--r--src/eval/template.rs50
-rw-r--r--src/eval/value.rs132
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");
+ }
+}