diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 7f4426ff3..b0d5fb825 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::fmt::{self, Debug, Display, Formatter}; +use std::iter::Extend; use std::ops::Index; use crate::syntax::{Span, Spanned}; @@ -117,9 +118,7 @@ impl Dict { /// Iterator over all borrowed keys and values. pub fn iter(&self) -> impl Iterator { - self.nums() - .map(|(&k, v)| (RefKey::Num(k), v)) - .chain(self.strs().map(|(k, v)| (RefKey::Str(k), v))) + self.into_iter() } /// Iterate over all values in the dictionary. @@ -137,14 +136,6 @@ impl Dict { self.strs.iter() } - /// Move into an owned iterator over owned keys and values. - pub fn into_iter(self) -> impl Iterator { - self.nums - .into_iter() - .map(|(k, v)| (DictKey::Num(k), v)) - .chain(self.strs.into_iter().map(|(k, v)| (DictKey::Str(k), v))) - } - /// Move into an owned iterator over all values in the dictionary. pub fn into_values(self) -> impl Iterator { self.nums @@ -164,17 +155,6 @@ impl Dict { } } -impl<'a, K, V> Index for Dict -where - K: Into>, -{ - type Output = V; - - fn index(&self, index: K) -> &Self::Output { - self.get(index).expect("key not in dict") - } -} - impl Default for Dict { fn default() -> Self { Self::new() @@ -189,6 +169,68 @@ impl PartialEq for Dict { } } +impl IntoIterator for Dict { + type Item = (DictKey, V); + type IntoIter = std::iter::Chain< + std::iter::Map< + std::collections::btree_map::IntoIter, + fn((u64, V)) -> (DictKey, V), + >, + std::iter::Map< + std::collections::btree_map::IntoIter, + fn((String, V)) -> (DictKey, V), + >, + >; + + fn into_iter(self) -> Self::IntoIter { + let nums = self.nums.into_iter().map((|(k, v)| (DictKey::Num(k), v)) as _); + let strs = self.strs.into_iter().map((|(k, v)| (DictKey::Str(k), v)) as _); + nums.chain(strs) + } +} + +impl<'a, V> IntoIterator for &'a Dict { + type Item = (RefKey<'a>, &'a V); + type IntoIter = std::iter::Chain< + std::iter::Map< + std::collections::btree_map::Iter<'a, u64, V>, + fn((&'a u64, &'a V)) -> (RefKey<'a>, &'a V), + >, + std::iter::Map< + std::collections::btree_map::Iter<'a, String, V>, + fn((&'a String, &'a V)) -> (RefKey<'a>, &'a V), + >, + >; + + fn into_iter(self) -> Self::IntoIter { + let strs = self.strs().map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _); + let nums = self.nums().map((|(k, v): (&u64, _)| (RefKey::Num(*k), v)) as _); + nums.chain(strs) + } +} + +impl Extend<(DictKey, V)> for Dict { + fn extend(&mut self, iter: T) + where + T: IntoIterator, + { + for (key, value) in iter.into_iter() { + self.insert(key, value); + } + } +} + +impl<'a, K, V> Index for Dict +where + K: Into>, +{ + type Output = V; + + fn index(&self, index: K) -> &Self::Output { + self.get(index).expect("key not in dict") + } +} + impl Debug for Dict { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if self.is_empty() { diff --git a/src/eval/value.rs b/src/eval/value.rs index 51bc55ab0..85ac2367e 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -17,6 +17,8 @@ use crate::{DynFuture, Feedback, Pass}; /// A computational value. #[derive(Clone, PartialEq)] pub enum Value { + /// The result of invalid operations. + Error, /// An identifier: `ident`. Ident(Ident), /// A boolean: `true, false`. @@ -54,6 +56,7 @@ impl Value { /// The natural-language name of this value for use in error messages. pub fn name(&self) -> &'static str { match self { + Self::Error => "error", Self::Ident(_) => "identifier", Self::Bool(_) => "bool", Self::Int(_) => "integer", @@ -111,6 +114,7 @@ impl Spanned { impl Debug for Value { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { + Self::Error => f.pad(""), Self::Ident(i) => i.fmt(f), Self::Bool(b) => b.fmt(f), Self::Int(i) => i.fmt(f), diff --git a/src/geom.rs b/src/geom.rs index 05143f22a..0a65a0902 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -299,6 +299,14 @@ impl DivAssign for Linear { } } +impl Neg for Linear { + type Output = Self; + + fn neg(self) -> Self { + Self { rel: -self.rel, abs: -self.abs } + } +} + impl Debug for Linear { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}x + {}", self.rel, self.abs) diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 0f6fb3781..fbfb71e68 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -245,9 +245,9 @@ impl<'s> Tokens<'s> { self.s.eat_until(|c| { let end = match c { c if c.is_whitespace() => true, - '[' | ']' | '*' | '/' => true, + '[' | ']' | '{' | '}' | '*' | '/' | '#' => true, '_' | '`' | '~' | '\\' if body => true, - '(' | ')' | '{' | '}' | ':' | ',' | '=' | '"' | '#' if header => true, + '(' | ')' | ':' | ',' | '=' | '"' if header => true, '+' | '-' if header && !last_was_e => true, _ => false, }; @@ -360,19 +360,21 @@ mod tests { #[test] fn tokenize_body_tokens() { - t!(Body, "_*" => Underscore, Star); - t!(Body, "***" => Star, Star, Star); + t!(Body, "a_*" => T("a"), Underscore, Star); + t!(Body, "a***" => T("a"), Star, Star, Star); t!(Body, "[func]*bold*" => L, T("func"), R, Star, T("bold"), Star); t!(Body, "hi_you_ there" => T("hi"), Underscore, T("you"), Underscore, S(0), T("there")); t!(Body, "# hi" => Hashtag, S(0), T("hi")); - t!(Body, "#()" => Hashtag, T("()")); + t!(Body, "ab# hi" => T("ab"), Hashtag, S(0), T("hi")); + t!(Body, "#{}" => Hashtag, LB, RB); + t!(Body, "{text}" => LB, Text("text"), RB); t!(Header, "_`" => Invalid("_`")); } #[test] fn test_tokenize_raw() { // Basics. - t!(Body, "`raw`" => Raw("raw", 1, true)); + t!(Body, "a`raw`" => T("a"), Raw("raw", 1, true)); t!(Body, "`[func]`" => Raw("[func]", 1, true)); t!(Body, "`]" => Raw("]", 1, false)); t!(Body, r"`\`` " => Raw(r"\", 1, true), Raw(" ", 1, false)); diff --git a/src/syntax/ast/expr.rs b/src/syntax/ast/expr.rs index c07c6216c..4f0671459 100644 --- a/src/syntax/ast/expr.rs +++ b/src/syntax/ast/expr.rs @@ -3,7 +3,7 @@ use crate::eval::Value; use crate::layout::LayoutContext; use crate::syntax::{Decoration, Ident, Lit, LitDict, SpanWith, Spanned}; -use crate::Feedback; +use crate::{DynFuture, Feedback}; /// An expression. #[derive(Debug, Clone, PartialEq)] @@ -20,13 +20,19 @@ pub enum Expr { impl Expr { /// Evaluate the expression to a value. - pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { - match self { - Self::Lit(lit) => lit.eval(ctx, f).await, - Self::Unary(unary) => unary.eval(ctx, f).await, - Self::Binary(binary) => binary.eval(ctx, f).await, - Self::Call(call) => call.eval(ctx, f).await, - } + pub fn eval<'a>( + &'a self, + ctx: &'a LayoutContext<'a>, + f: &'a mut Feedback, + ) -> DynFuture<'a, Value> { + Box::pin(async move { + match self { + Self::Lit(lit) => lit.eval(ctx, f).await, + Self::Unary(unary) => unary.eval(ctx, f).await, + Self::Binary(binary) => binary.eval(ctx, f).await, + Self::Call(call) => call.eval(ctx, f).await, + } + }) } } @@ -41,9 +47,27 @@ pub struct ExprUnary { impl ExprUnary { /// Evaluate the expression to a value. - pub async fn eval(&self, _: &LayoutContext<'_>, _: &mut Feedback) -> Value { + pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { + use Value::*; + + let value = self.expr.v.eval(ctx, f).await; + if value == Error { + return Error; + } + + let span = self.op.span.join(self.expr.span); match self.op.v { - UnOp::Neg => todo!("eval neg"), + UnOp::Neg => match value { + Int(x) => Int(-x), + Float(x) => Float(-x), + Length(x) => Length(-x), + Relative(x) => Relative(-x), + Linear(x) => Linear(-x), + v => { + error!(@f, span, "cannot negate {}", v.name()); + Value::Error + } + }, } } } @@ -68,16 +92,138 @@ pub struct ExprBinary { impl ExprBinary { /// Evaluate the expression to a value. - pub async fn eval(&self, _: &LayoutContext<'_>, _: &mut Feedback) -> Value { + pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { + use crate::geom::Linear as Lin; + use Value::*; + + let lhs = self.lhs.v.eval(ctx, f).await; + let rhs = self.rhs.v.eval(ctx, f).await; + + if lhs == Error || rhs == Error { + return Error; + } + + let span = self.lhs.span.join(self.rhs.span); match self.op.v { - BinOp::Add => todo!("eval add"), - BinOp::Sub => todo!("eval sub"), - BinOp::Mul => todo!("eval mul"), - BinOp::Div => todo!("eval div"), + BinOp::Add => match (lhs, rhs) { + // Numbers to themselves. + (Int(a), Int(b)) => Int(a + b), + (Int(i), Float(f)) | (Float(f), Int(i)) => Float(i as f64 + f), + (Float(a), Float(b)) => Float(a + b), + + // Lengths, relatives and linears to themselves. + (Length(a), Length(b)) => Length(a + b), + (Length(a), Relative(b)) => Linear(Lin::abs(a) + Lin::rel(b)), + (Length(a), Linear(b)) => Linear(Lin::abs(a) + b), + + (Relative(a), Length(b)) => Linear(Lin::rel(a) + Lin::abs(b)), + (Relative(a), Relative(b)) => Relative(a + b), + (Relative(a), Linear(b)) => Linear(Lin::rel(a) + b), + + (Linear(a), Length(b)) => Linear(a + Lin::abs(b)), + (Linear(a), Relative(b)) => Linear(a + Lin::rel(b)), + (Linear(a), Linear(b)) => Linear(a + b), + + // Complex data types to themselves. + (Str(a), Str(b)) => Str(a + &b), + (Dict(a), Dict(b)) => Dict(concat(a, b)), + (Tree(a), Tree(b)) => Tree(concat(a, b)), + (Commands(a), Commands(b)) => Commands(concat(a, b)), + + (a, b) => { + error!(@f, span, "cannot add {} and {}", a.name(), b.name()); + Value::Error + } + }, + + BinOp::Sub => match (lhs, rhs) { + // Numbers from themselves. + (Int(a), Int(b)) => Int(a - b), + (Int(a), Float(b)) => Float(a as f64 - b), + (Float(a), Int(b)) => Float(a - b as f64), + (Float(a), Float(b)) => Float(a - b), + + // Lengths, relatives and linears from themselves. + (Length(a), Length(b)) => Length(a - b), + (Length(a), Relative(b)) => Linear(Lin::abs(a) - Lin::rel(b)), + (Length(a), Linear(b)) => Linear(Lin::abs(a) - b), + (Relative(a), Length(b)) => Linear(Lin::rel(a) - Lin::abs(b)), + (Relative(a), Relative(b)) => Relative(a - b), + (Relative(a), Linear(b)) => Linear(Lin::rel(a) - b), + (Linear(a), Length(b)) => Linear(a - Lin::abs(b)), + (Linear(a), Relative(b)) => Linear(a - Lin::rel(b)), + (Linear(a), Linear(b)) => Linear(a - b), + + (a, b) => { + error!(@f, span, "cannot subtract {1} from {0}", a.name(), b.name()); + Value::Error + } + }, + + BinOp::Mul => match (lhs, rhs) { + // Numbers with themselves. + (Int(a), Int(b)) => Int(a * b), + (Int(a), Float(b)) => Float(a as f64 * b), + (Float(a), Int(b)) => Float(a * b as f64), + (Float(a), Float(b)) => Float(a * b), + + // Lengths, relatives and linears with numbers. + (Length(a), Int(b)) => Length(a * b as f64), + (Length(a), Float(b)) => Length(a * b), + (Int(a), Length(b)) => Length(a as f64 * b), + (Float(a), Length(b)) => Length(a * b), + (Relative(a), Int(b)) => Relative(a * b as f64), + (Relative(a), Float(b)) => Relative(a * b), + (Int(a), Relative(b)) => Relative(a as f64 * b), + (Float(a), Relative(b)) => Relative(a * b), + (Linear(a), Int(b)) => Linear(a * b as f64), + (Linear(a), Float(b)) => Linear(a * b), + (Int(a), Linear(b)) => Linear(a as f64 * b), + (Float(a), Linear(b)) => Linear(a * b), + + // Integers with strings. + (Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)), + (Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)), + + (a, b) => { + error!(@f, span, "cannot multiply {} with {}", a.name(), b.name()); + Value::Error + } + }, + + BinOp::Div => match (lhs, rhs) { + // Numbers by themselves. + (Int(a), Int(b)) => Float(a as f64 / b as f64), + (Int(a), Float(b)) => Float(a as f64 / b), + (Float(a), Int(b)) => Float(a / b as f64), + (Float(a), Float(b)) => Float(a / b), + + // Lengths by numbers. + (Length(a), Int(b)) => Length(a / b as f64), + (Length(a), Float(b)) => Length(a / b), + (Relative(a), Int(b)) => Relative(a / b as f64), + (Relative(a), Float(b)) => Relative(a / b), + (Linear(a), Int(b)) => Linear(a / b as f64), + (Linear(a), Float(b)) => Linear(a / b), + + (a, b) => { + error!(@f, span, "cannot divide {} by {}", a.name(), b.name()); + Value::Error + } + }, } } } +/// Concatenate two collections. +fn concat(mut a: T, b: T) -> T +where + T: Extend + IntoIterator, +{ + a.extend(b); + a +} + /// A binary operator. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum BinOp { diff --git a/src/syntax/ast/lit.rs b/src/syntax/ast/lit.rs index ba7e7e4fe..c08dc8cd6 100644 --- a/src/syntax/ast/lit.rs +++ b/src/syntax/ast/lit.rs @@ -39,11 +39,7 @@ pub enum Lit { impl Lit { /// Evaluate the dictionary literal to a dictionary value. - pub async fn eval<'a>( - &'a self, - ctx: &'a LayoutContext<'a>, - f: &'a mut Feedback, - ) -> Value { + pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { match *self { Lit::Ident(ref i) => Value::Ident(i.clone()), Lit::Bool(b) => Value::Bool(b),