diff --git a/src/eco.rs b/src/eco.rs index 2d2ab2dc0..a52a163c4 100644 --- a/src/eco.rs +++ b/src/eco.rs @@ -7,7 +7,7 @@ use std::hash::{Hash, Hasher}; use std::ops::{Add, AddAssign, Deref}; use std::rc::Rc; -/// A economical string with inline storage and clone-on-write value semantics. +/// An economical string with inline storage and clone-on-write value semantics. #[derive(Clone)] pub struct EcoString(Repr); @@ -22,8 +22,9 @@ enum Repr { /// The maximum number of bytes that can be stored inline. /// -/// The value is chosen such that `Repr` fits exactly into 16 bytes -/// (which are needed anyway due to `Rc`s alignment). +/// The value is chosen such that an `EcoString` fits exactly into 16 bytes +/// (which are needed anyway due to the `Rc`s alignment, at least on 64-bit +/// platforms). /// /// Must be at least 4 to hold any char. const LIMIT: usize = 14; @@ -77,7 +78,7 @@ impl EcoString { self } - /// Appends the given character at the end. + /// Append the given character at the end. pub fn push(&mut self, c: char) { match &mut self.0 { Repr::Small { buf, len } => { @@ -93,7 +94,7 @@ impl EcoString { } } - /// Appends the given string slice at the end. + /// Append the given string slice at the end. pub fn push_str(&mut self, string: &str) { match &mut self.0 { Repr::Small { buf, len } => { @@ -113,7 +114,7 @@ impl EcoString { } } - /// Removes the last character from the string. + /// Remove the last character from the string. pub fn pop(&mut self) -> Option { let c = self.as_str().chars().rev().next()?; match &mut self.0 { @@ -127,7 +128,21 @@ impl EcoString { Some(c) } - /// Repeats this string `n` times. + /// Clear the string. + pub fn clear(&mut self) { + match &mut self.0 { + Repr::Small { len, .. } => *len = 0, + Repr::Large(rc) => { + if Rc::strong_count(rc) == 1 { + Rc::make_mut(rc).clear(); + } else { + *self = Self::new(); + } + } + } + } + + /// Repeat this string `n` times. pub fn repeat(&self, n: usize) -> Self { if let Repr::Small { buf, len } = &self.0 { let prev = usize::from(*len); @@ -209,18 +224,18 @@ impl Default for EcoString { } } -impl Debug for EcoString { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(self.as_str(), f) - } -} - impl Display for EcoString { fn fmt(&self, f: &mut Formatter) -> fmt::Result { Display::fmt(self.as_str(), f) } } +impl Debug for EcoString { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Debug::fmt(self.as_str(), f) + } +} + impl Eq for EcoString {} impl PartialEq for EcoString { @@ -259,6 +274,14 @@ impl PartialOrd for EcoString { } } +impl Add<&Self> for EcoString { + type Output = Self; + + fn add(self, rhs: &Self) -> Self::Output { + self + rhs.as_str() + } +} + impl Add<&str> for EcoString { type Output = Self; diff --git a/src/eval/array.rs b/src/eval/array.rs new file mode 100644 index 000000000..f62bda9f9 --- /dev/null +++ b/src/eval/array.rs @@ -0,0 +1,148 @@ +use std::fmt::{self, Debug, Formatter}; +use std::iter::FromIterator; +use std::ops::{Add, AddAssign}; +use std::rc::Rc; + +use super::Value; + +/// Create a new [`Array`] from values. +#[macro_export] +macro_rules! array { + ($value:expr; $count:expr) => { + $crate::eval::Array::from_vec(vec![$crate::eval::Value::from($value); $count]) + }; + + ($($value:expr),* $(,)?) => { + $crate::eval::Array::from_vec(vec![$($crate::eval::Value::from($value)),*]) + }; +} + +/// A variably-typed array with clone-on-write value semantics. +#[derive(Clone, PartialEq)] +pub struct Array { + vec: Rc>, +} + +impl Array { + /// Create a new, empty array. + pub fn new() -> Self { + Self { vec: Rc::new(vec![]) } + } + + /// Create a new array from a vector of values. + pub fn from_vec(vec: Vec) -> Self { + 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 + } + + /// The length of the array. + pub fn len(&self) -> usize { + self.vec.len() + } + + /// Borrow the value at the given index. + pub fn get(&self, index: usize) -> Option<&Value> { + self.vec.get(index) + } + + /// Mutably borrow the value at the given index. + pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> { + Rc::make_mut(&mut self.vec).get_mut(index) + } + + /// Set the value at the given index. + /// + /// This panics the `index` is out of range. + pub fn set(&mut self, index: usize, value: Value) { + Rc::make_mut(&mut self.vec)[index] = value; + } + + /// Push a value to the end of the array. + pub fn push(&mut self, value: Value) { + Rc::make_mut(&mut self.vec).push(value); + } + + /// Extend the array with the values from another array. + pub fn extend(&mut self, other: &Array) { + Rc::make_mut(&mut self.vec).extend(other.into_iter()) + } + + /// Clear the array. + pub fn clear(&mut self) { + if Rc::strong_count(&mut self.vec) == 1 { + Rc::make_mut(&mut self.vec).clear(); + } else { + *self = Self::new(); + } + } + + /// Repeat this array `n` times. + pub fn repeat(&self, n: usize) -> Self { + let len = self.len().checked_mul(n).expect("capacity overflow"); + self.into_iter().cycle().take(len).collect() + } + + /// Iterate over references to the contained values. + pub fn iter(&self) -> std::slice::Iter { + self.vec.iter() + } + + /// Iterate over the contained values. + pub fn into_iter(&self) -> impl Iterator + Clone + '_ { + // TODO: Actually consume the vector if the ref-count is 1? + self.iter().cloned() + } +} + +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 FromIterator for Array { + fn from_iter>(iter: T) -> Self { + Array { vec: Rc::new(iter.into_iter().collect()) } + } +} + +impl<'a> IntoIterator for &'a Array { + type Item = &'a Value; + type IntoIter = std::slice::Iter<'a, Value>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl Add<&Array> for Array { + type Output = Self; + + fn add(mut self, rhs: &Array) -> Self::Output { + self.extend(rhs); + self + } +} + +impl AddAssign<&Array> for Array { + fn add_assign(&mut self, rhs: &Array) { + self.extend(rhs); + } +} diff --git a/src/eval/dict.rs b/src/eval/dict.rs new file mode 100644 index 000000000..bf99ea17e --- /dev/null +++ b/src/eval/dict.rs @@ -0,0 +1,129 @@ +use std::collections::BTreeMap; +use std::fmt::{self, Debug, Formatter}; +use std::iter::FromIterator; +use std::ops::{Add, AddAssign}; +use std::rc::Rc; + +use super::Value; +use crate::eco::EcoString; + +/// Create a new [`Dict`] from key-value pairs. +#[macro_export] +macro_rules! dict { + ($($key:expr => $value:expr),* $(,)?) => {{ + #[allow(unused_mut)] + let mut map = std::collections::BTreeMap::new(); + $(map.insert($crate::eco::EcoString::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)] +pub struct Dict { + map: Rc>, +} + +impl Dict { + /// Create a new, empty dictionary. + pub fn new() -> Self { + Self::default() + } + + /// Create a new dictionary from a mapping of strings to values. + 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 + } + + /// The number of pairs in the dictionary. + pub fn len(&self) -> usize { + self.map.len() + } + + /// Borrow the value the given `key` maps to. + pub fn get(&self, key: &str) -> Option<&Value> { + self.map.get(key) + } + + /// Mutably borrow the value the given `key` maps to. + pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> { + Rc::make_mut(&mut self.map).get_mut(key) + } + + /// 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); + } + + /// Extend the dictionary with the values from another dictionary. + pub fn extend(&mut self, other: &Dict) { + Rc::make_mut(&mut self.map).extend(other.into_iter()) + } + + /// Clear the dictionary. + pub fn clear(&mut self) { + if Rc::strong_count(&mut self.map) == 1 { + Rc::make_mut(&mut self.map).clear(); + } else { + *self = Self::new(); + } + } + + /// Iterate over pairs of the contained keys and values. + pub fn into_iter(&self) -> impl Iterator + Clone + '_ { + // TODO: Actually consume the map if the ref-count is 1? + self.iter().map(|(k, v)| (k.clone(), v.clone())) + } + + /// Iterate over pairs of references to the contained keys and values. + pub fn iter(&self) -> std::collections::btree_map::Iter { + self.map.iter() + } +} + +impl Default for Dict { + fn default() -> Self { + Self::new() + } +} + +impl Debug for Dict { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_map().entries(self.map.iter()).finish() + } +} + +impl FromIterator<(EcoString, Value)> for Dict { + fn from_iter>(iter: T) -> Self { + Dict { map: Rc::new(iter.into_iter().collect()) } + } +} + +impl<'a> IntoIterator for &'a Dict { + type Item = (&'a EcoString, &'a Value); + type IntoIter = std::collections::btree_map::Iter<'a, EcoString, Value>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl Add<&Dict> for Dict { + type Output = Self; + + fn add(mut self, rhs: &Dict) -> Self::Output { + self.extend(rhs); + self + } +} + +impl AddAssign<&Dict> for Dict { + fn add_assign(&mut self, rhs: &Dict) { + self.extend(rhs); + } +} diff --git a/src/eval/function.rs b/src/eval/function.rs new file mode 100644 index 000000000..c986d71a9 --- /dev/null +++ b/src/eval/function.rs @@ -0,0 +1,176 @@ +use std::fmt::{self, Debug, Formatter}; +use std::ops::Deref; +use std::rc::Rc; + +use super::{Cast, CastResult, EvalContext, Value}; +use crate::eco::EcoString; +use crate::syntax::{Span, Spanned}; + +/// An evaluatable function. +#[derive(Clone)] +pub struct Function { + /// The name of the function. + /// + /// The string is boxed to make the whole struct fit into 24 bytes, so that + /// a value fits into 32 bytes. + name: Option>, + /// The closure that defines the function. + f: Rc Value>, +} + +impl Function { + /// Create a new function from a rust closure. + pub fn new(name: Option, f: F) -> Self + where + F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static, + { + Self { name: name.map(Box::new), f: Rc::new(f) } + } + + /// The name of the function. + pub fn name(&self) -> Option<&EcoString> { + self.name.as_ref().map(|s| &**s) + } +} + +impl Deref for Function { + type Target = dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value; + + fn deref(&self) -> &Self::Target { + self.f.as_ref() + } +} + +impl Debug for Function { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("ValueFunc").field("name", &self.name).finish() + } +} + +impl PartialEq for Function { + fn eq(&self, other: &Self) -> bool { + // We cast to thin pointers because we don't want to compare vtables. + Rc::as_ptr(&self.f) as *const () == Rc::as_ptr(&other.f) as *const () + } +} + +/// Evaluated arguments to a function. +#[derive(Debug, Clone, PartialEq)] +pub struct FuncArgs { + /// The span of the whole argument list. + pub span: Span, + /// The positional arguments. + pub items: Vec, +} + +/// An argument to a function call: `12` or `draw: false`. +#[derive(Debug, Clone, PartialEq)] +pub struct FuncArg { + /// The span of the whole argument. + pub span: Span, + /// The name of the argument (`None` for positional arguments). + pub name: Option, + /// The value of the argument. + pub value: Spanned, +} + +impl FuncArgs { + /// Find and consume the first castable positional argument. + pub fn eat(&mut self, ctx: &mut EvalContext) -> Option + where + T: Cast>, + { + (0 .. self.items.len()).find_map(|index| { + let slot = &mut self.items[index]; + if slot.name.is_some() { + return None; + } + + let value = std::mem::replace(&mut slot.value, Spanned::zero(Value::None)); + let span = value.span; + + match T::cast(value) { + CastResult::Ok(t) => { + self.items.remove(index); + Some(t) + } + CastResult::Warn(t, m) => { + self.items.remove(index); + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + slot.value = value; + None + } + } + }) + } + + /// Find and consume the first castable positional argument, producing a + /// `missing argument: {what}` error if no match was found. + pub fn expect(&mut self, ctx: &mut EvalContext, what: &str) -> Option + where + T: Cast>, + { + let found = self.eat(ctx); + if found.is_none() { + ctx.diag(error!(self.span, "missing argument: {}", what)); + } + found + } + + /// Find, consume and collect all castable positional arguments. + /// + /// This function returns a vector instead of an iterator because the + /// iterator would require unique access to the context, rendering it rather + /// unusable. If you need to process arguments one-by-one, you probably want + /// to use a while-let loop together with [`eat()`](Self::eat). + pub fn all(&mut self, ctx: &mut EvalContext) -> Vec + where + T: Cast>, + { + std::iter::from_fn(|| self.eat(ctx)).collect() + } + + /// Cast and remove the value for the given named argument, producing an + /// error if the conversion fails. + pub fn named(&mut self, ctx: &mut EvalContext, name: &str) -> Option + where + T: Cast>, + { + let index = self + .items + .iter() + .position(|arg| arg.name.as_ref().map_or(false, |other| other == name))?; + + let value = self.items.remove(index).value; + let span = value.span; + + match T::cast(value) { + CastResult::Ok(t) => Some(t), + CastResult::Warn(t, m) => { + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + ctx.diag(error!( + span, + "expected {}, found {}", + T::TYPE_NAME, + value.v.type_name(), + )); + None + } + } + } + + /// Produce "unexpected argument" errors for all remaining arguments. + pub fn finish(self, ctx: &mut EvalContext) { + for arg in &self.items { + if arg.value.v != Value::Error { + ctx.diag(error!(arg.span, "unexpected argument")); + } + } + } +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index dc4ab7ee6..689234bd7 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,13 +1,23 @@ //! Evaluation of syntax trees. +#[macro_use] +mod array; +#[macro_use] +mod dict; #[macro_use] mod value; mod capture; +mod function; mod ops; mod scope; +mod template; +pub use array::*; pub use capture::*; +pub use dict::*; +pub use function::*; pub use scope::*; +pub use template::*; pub use value::*; use std::collections::HashMap; @@ -45,7 +55,7 @@ pub struct Module { /// The top-level definitions that were bound in this module. pub scope: Scope, /// The template defined by this module. - pub template: TemplateValue, + pub template: Template, } /// The context for evaluation. @@ -213,7 +223,7 @@ pub trait Eval { } impl Eval for Rc { - type Output = TemplateValue; + type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { struct ExprVisitor<'a, 'b> { @@ -230,10 +240,7 @@ impl Eval for Rc { let mut visitor = ExprVisitor { ctx, map: ExprMap::new() }; visitor.visit_tree(self); - Rc::new(vec![TemplateNode::Tree { - tree: Rc::clone(self), - map: visitor.map, - }]) + TemplateTree { tree: Rc::clone(self), map: visitor.map }.into() } } @@ -280,7 +287,7 @@ impl Eval for Expr { } impl Eval for ArrayExpr { - type Output = ArrayValue; + type Output = Array; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { self.items.iter().map(|expr| expr.eval(ctx)).collect() @@ -288,7 +295,7 @@ impl Eval for ArrayExpr { } impl Eval for DictExpr { - type Output = DictValue; + type Output = Dict; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { self.items @@ -299,7 +306,7 @@ impl Eval for DictExpr { } impl Eval for TemplateExpr { - type Output = TemplateValue; + type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { self.tree.eval(ctx) @@ -476,7 +483,7 @@ impl Eval for CallExpr { fn eval(&self, ctx: &mut EvalContext) -> Self::Output { let callee = self.callee.eval(ctx); - if let Some(func) = ctx.cast::(callee, self.callee.span()) { + if let Some(func) = ctx.cast::(callee, self.callee.span()) { let mut args = self.args.eval(ctx); let returned = func(ctx, &mut args); args.finish(ctx); @@ -530,7 +537,7 @@ impl Eval for ClosureExpr { }; let name = self.name.as_ref().map(|name| name.string.clone()); - Value::Func(FuncValue::new(name, move |ctx, args| { + Value::Func(Function::new(name, move |ctx, args| { // Don't leak the scopes from the call site. Instead, we use the // scope of captured variables we collected earlier. let prev = mem::take(&mut ctx.scopes); @@ -554,10 +561,10 @@ impl Eval for WithExpr { fn eval(&self, ctx: &mut EvalContext) -> Self::Output { let callee = self.callee.eval(ctx); - if let Some(func) = ctx.cast::(callee, self.callee.span()) { + if let Some(func) = ctx.cast::(callee, self.callee.span()) { let applied = self.args.eval(ctx); let name = func.name().cloned(); - Value::Func(FuncValue::new(name, move |ctx, args| { + Value::Func(Function::new(name, move |ctx, args| { // Remove named arguments that were overridden. let kept: Vec<_> = applied .items diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 3b48140c9..c1dd726b6 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,7 +1,6 @@ use std::cmp::Ordering::*; -use std::rc::Rc; -use super::{TemplateNode, Value}; +use super::Value; use Value::*; /// Apply the plus operator to a value. @@ -90,11 +89,6 @@ pub fn sub(lhs: Value, rhs: Value) -> Value { /// Compute the product of two values. pub fn mul(lhs: Value, rhs: Value) -> Value { - fn repeat(vec: Vec, n: usize) -> Vec { - let len = n * vec.len(); - vec.into_iter().cycle().take(len).collect() - } - match (lhs, rhs) { (Int(a), Int(b)) => Int(a * b), (Int(a), Float(b)) => Float(a as f64 * b), @@ -128,8 +122,8 @@ pub fn mul(lhs: Value, rhs: Value) -> Value { (Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)), (Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)), - (Array(a), Int(b)) => Array(repeat(a, b.max(0) as usize)), - (Int(a), Array(b)) => Array(repeat(b, a.max(0) as usize)), + (Array(a), Int(b)) => Array(a.repeat(b.max(0) as usize)), + (Int(a), Array(b)) => Array(b.repeat(a.max(0) as usize)), _ => Error, } @@ -240,26 +234,11 @@ pub fn join(lhs: Value, rhs: Value) -> Result { fn concat(lhs: Value, rhs: Value) -> Result { Ok(match (lhs, rhs) { (Str(a), Str(b)) => Str(a + &b), - (Array(mut a), Array(b)) => Array({ - a.extend(b); - a - }), - (Dict(mut a), Dict(b)) => Dict({ - a.extend(b); - a - }), - (Template(mut a), Template(b)) => Template({ - Rc::make_mut(&mut a).extend(b.iter().cloned()); - a - }), - (Template(mut a), Str(b)) => Template({ - Rc::make_mut(&mut a).push(TemplateNode::Str(b)); - a - }), - (Str(a), Template(mut b)) => Template({ - Rc::make_mut(&mut b).insert(0, TemplateNode::Str(a)); - b - }), + (Array(a), Array(b)) => Array(a + &b), + (Dict(a), Dict(b)) => Dict(a + &b), + (Template(a), Template(b)) => Template(a + &b), + (Template(a), Str(b)) => Template(a + b), + (Str(a), Template(b)) => Template(a + b), (a, _) => return Err(a), }) } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 05bbeda24..4a9d59704 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::iter; use std::rc::Rc; -use super::{AnyValue, EcoString, EvalContext, FuncArgs, FuncValue, Type, Value}; +use super::{AnyValue, EcoString, EvalContext, FuncArgs, Function, Type, Value}; /// A slot where a variable is stored. pub type Slot = Rc>; @@ -92,7 +92,7 @@ impl Scope { F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static, { let name = name.into(); - self.def_const(name.clone(), FuncValue::new(Some(name), f)); + self.def_const(name.clone(), Function::new(Some(name), f)); } /// Define a constant variable with a value of variant `Value::Any`. diff --git a/src/eval/template.rs b/src/eval/template.rs new file mode 100644 index 000000000..4d0039982 --- /dev/null +++ b/src/eval/template.rs @@ -0,0 +1,139 @@ +use std::collections::HashMap; +use std::fmt::{self, Debug, Formatter}; +use std::ops::{Add, Deref}; +use std::rc::Rc; + +use super::Value; +use crate::eco::EcoString; +use crate::exec::ExecContext; +use crate::syntax::{Expr, SyntaxTree}; + +/// A template value: `[*Hi* there]`. +#[derive(Default, Debug, Clone)] +pub struct Template { + nodes: Rc>, +} + +impl Template { + /// Create a new template from a vector of nodes. + pub fn new(nodes: Vec) -> Self { + Self { nodes: Rc::new(nodes) } + } + + /// Iterate over the contained template nodes. + pub fn iter(&self) -> impl Iterator + '_ { + self.nodes.iter() + } +} + +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 PartialEq for Template { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.nodes, &other.nodes) + } +} + +impl Add<&Template> for Template { + type Output = Self; + + fn add(mut self, rhs: &Self) -> Self::Output { + Rc::make_mut(&mut self.nodes).extend(rhs.nodes.iter().cloned()); + self + } +} + +impl Add for Template { + type Output = Self; + + fn add(mut self, rhs: EcoString) -> Self::Output { + Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs)); + self + } +} + +impl Add