diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/args.rs | 19 | ||||
| -rw-r--r-- | src/eval/array.rs | 145 | ||||
| -rw-r--r-- | src/eval/dict.rs | 43 | ||||
| -rw-r--r-- | src/eval/mod.rs | 150 | ||||
| -rw-r--r-- | src/eval/ops.rs | 19 | ||||
| -rw-r--r-- | src/eval/scope.rs | 8 | ||||
| -rw-r--r-- | src/eval/str.rs | 37 | ||||
| -rw-r--r-- | src/eval/value.rs | 121 | ||||
| -rw-r--r-- | src/geom/angle.rs | 2 | ||||
| -rw-r--r-- | src/library/mod.rs | 9 | ||||
| -rw-r--r-- | src/library/utility/color.rs | 58 | ||||
| -rw-r--r-- | src/library/utility/math.rs | 29 | ||||
| -rw-r--r-- | src/library/utility/mod.rs | 180 | ||||
| -rw-r--r-- | src/library/utility/string.rs (renamed from src/library/utility/numbering.rs) | 37 | ||||
| -rw-r--r-- | src/parse/incremental.rs | 8 | ||||
| -rw-r--r-- | src/parse/mod.rs | 94 | ||||
| -rw-r--r-- | src/parse/parser.rs | 11 | ||||
| -rw-r--r-- | src/parse/tokens.rs | 6 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 75 | ||||
| -rw-r--r-- | src/syntax/highlight.rs | 27 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 24 |
21 files changed, 706 insertions, 396 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs index 67da9865..40454ff5 100644 --- a/src/eval/args.rs +++ b/src/eval/args.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Debug, Formatter, Write}; -use super::{Cast, Value}; +use super::{Array, Cast, Dict, Value}; use crate::diag::{At, TypResult}; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; @@ -147,6 +147,23 @@ impl Args { Ok(()) } + /// Extract the positional arguments as an array. + pub fn to_positional(&self) -> Array { + self.items + .iter() + .filter(|item| item.name.is_none()) + .map(|item| item.value.v.clone()) + .collect() + } + + /// Extract the named arguments as a dictionary. + pub fn to_named(&self) -> Dict { + self.items + .iter() + .filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone()))) + .collect() + } + /// Reinterpret these arguments as actually being an array index. pub fn into_index(self) -> TypResult<i64> { self.into_castable("index") diff --git a/src/eval/array.rs b/src/eval/array.rs index 2da1a5f4..6fb278e3 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -3,9 +3,11 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::Value; -use crate::diag::StrResult; +use super::{Args, Func, Value}; +use crate::diag::{At, StrResult, TypResult}; +use crate::syntax::Spanned; use crate::util::ArcExt; +use crate::Context; /// Create a new [`Array`] from values. #[allow(unused_macros)] @@ -66,36 +68,134 @@ impl Array { Arc::make_mut(&mut self.0).push(value); } + /// Remove the last value in the array. + pub fn pop(&mut self) -> StrResult<()> { + Arc::make_mut(&mut self.0).pop().ok_or_else(|| "array is empty")?; + Ok(()) + } + + /// Insert a value at the specified index. + pub fn insert(&mut self, index: i64, value: Value) -> StrResult<()> { + let len = self.len(); + let i = usize::try_from(index) + .ok() + .filter(|&i| i <= self.0.len()) + .ok_or_else(|| out_of_bounds(index, len))?; + + Arc::make_mut(&mut self.0).insert(i, value); + Ok(()) + } + + /// Remove and return the value at the specified index. + pub fn remove(&mut self, index: i64) -> StrResult<()> { + let len = self.len(); + let i = usize::try_from(index) + .ok() + .filter(|&i| i < self.0.len()) + .ok_or_else(|| out_of_bounds(index, len))?; + + Arc::make_mut(&mut self.0).remove(i); + return Ok(()); + } + /// Whether the array contains a specific value. pub fn contains(&self, value: &Value) -> bool { self.0.contains(value) } - /// Clear the array. - pub fn clear(&mut self) { - if Arc::strong_count(&self.0) == 1 { - Arc::make_mut(&mut self.0).clear(); - } else { - *self = Self::new(); + /// Extract a contigous subregion of the array. + pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { + let len = self.len(); + let start = usize::try_from(start) + .ok() + .filter(|&start| start <= self.0.len()) + .ok_or_else(|| out_of_bounds(start, len))?; + + let end = end.unwrap_or(self.len()); + let end = usize::try_from(end) + .ok() + .filter(|&end| end <= self.0.len()) + .ok_or_else(|| out_of_bounds(end, len))?; + + Ok(Self::from_vec(self.0[start .. end].to_vec())) + } + + /// Transform each item in the array with a function. + pub fn map(&self, ctx: &mut Context, f: Spanned<Func>) -> TypResult<Self> { + Ok(self + .iter() + .cloned() + .map(|item| f.v.call(ctx, Args::from_values(f.span, [item]))) + .collect::<TypResult<_>>()?) + } + + /// Return a new array with only those elements for which the function + /// return true. + pub fn filter(&self, ctx: &mut Context, f: Spanned<Func>) -> TypResult<Self> { + let mut kept = vec![]; + for item in self.iter() { + if f.v + .call(ctx, Args::from_values(f.span, [item.clone()]))? + .cast::<bool>() + .at(f.span)? + { + kept.push(item.clone()) + } } + Ok(Self::from_vec(kept)) } - /// Iterate over references to the contained values. - pub fn iter(&self) -> std::slice::Iter<Value> { - self.0.iter() + /// Return a new array with all items from this and nested arrays. + pub fn flatten(&self) -> Self { + let mut flat = vec![]; + for item in self.iter() { + if let Value::Array(nested) = item { + flat.extend(nested.flatten().into_iter()); + } else { + flat.push(item.clone()); + } + } + Self::from_vec(flat) } - /// Extracts a slice of the whole array. - pub fn as_slice(&self) -> &[Value] { - self.0.as_slice() + /// Return the index of the element if it is part of the array. + pub fn find(&self, value: Value) -> Option<i64> { + self.0.iter().position(|x| *x == value).map(|i| i as i64) + } + + /// Join all values in the array, optionally with separator and last + /// separator (between the final two items). + pub fn join(&self, sep: Option<Value>, mut last: Option<Value>) -> StrResult<Value> { + let len = self.0.len(); + let sep = sep.unwrap_or(Value::None); + + let mut result = Value::None; + for (i, value) in self.iter().cloned().enumerate() { + if i > 0 { + if i + 1 == len { + if let Some(last) = last.take() { + result = result.join(last)?; + } else { + result = result.join(sep.clone())?; + } + } else { + result = result.join(sep.clone())?; + } + } + + result = result.join(value)?; + } + + Ok(result) } /// Return a sorted version of this array. /// /// Returns an error if two values could not be compared. - pub fn sorted(mut self) -> StrResult<Self> { + pub fn sorted(&self) -> StrResult<Self> { let mut result = Ok(()); - Arc::make_mut(&mut self.0).sort_by(|a, b| { + let mut vec = (*self.0).clone(); + vec.sort_by(|a, b| { a.partial_cmp(b).unwrap_or_else(|| { if result.is_ok() { result = Err(format!( @@ -107,7 +207,7 @@ impl Array { Ordering::Equal }) }); - result.map(|_| self) + result.map(|_| Self::from_vec(vec)) } /// Repeat this array `n` times. @@ -119,6 +219,17 @@ impl Array { Ok(self.iter().cloned().cycle().take(count).collect()) } + + /// Extract a slice of the whole array. + pub fn as_slice(&self) -> &[Value] { + self.0.as_slice() + } + + + /// Iterate over references to the contained values. + pub fn iter(&self) -> std::slice::Iter<Value> { + self.0.iter() + } } /// The out of bounds access error message. diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 9127b2eb..b630fc63 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -3,9 +3,11 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::Value; -use crate::diag::StrResult; +use super::{Args, Array, Func, Value}; +use crate::diag::{StrResult, TypResult}; +use crate::syntax::Spanned; use crate::util::{ArcExt, EcoString}; +use crate::Context; /// Create a new [`Dict`] from key-value pairs. #[allow(unused_macros)] @@ -56,14 +58,22 @@ impl Dict { Arc::make_mut(&mut self.0).entry(key).or_default() } + /// Whether the dictionary contains a specific key. + pub fn contains(&self, key: &str) -> bool { + self.0.contains_key(key) + } + /// Insert a mapping from the given `key` to the given `value`. pub fn insert(&mut self, key: EcoString, value: Value) { Arc::make_mut(&mut self.0).insert(key, value); } - /// Whether the dictionary contains a specific key. - pub fn contains_key(&self, key: &str) -> bool { - self.0.contains_key(key) + /// Remove a mapping by `key`. + pub fn remove(&mut self, key: EcoString) -> StrResult<()> { + match Arc::make_mut(&mut self.0).remove(&key) { + Some(_) => Ok(()), + None => Err(missing_key(&key)), + } } /// Clear the dictionary. @@ -75,6 +85,29 @@ impl Dict { } } + /// Return the keys of the dictionary as an array. + pub fn keys(&self) -> Array { + self.iter().map(|(key, _)| Value::Str(key.clone())).collect() + } + + /// Return the values of the dictionary as an array. + pub fn values(&self) -> Array { + self.iter().map(|(_, value)| value.clone()).collect() + } + + /// Transform each pair in the array with a function. + pub fn map(&self, ctx: &mut Context, f: Spanned<Func>) -> TypResult<Array> { + Ok(self + .iter() + .map(|(key, value)| { + f.v.call( + ctx, + Args::from_values(f.span, [Value::Str(key.clone()), value.clone()]), + ) + }) + .collect::<TypResult<_>>()?) + } + /// Iterate over pairs of references to the contained keys and values. pub fn iter(&self) -> std::collections::btree_map::Iter<EcoString, Value> { self.0.iter() diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2c864036..564dca20 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -19,7 +19,9 @@ mod module; mod ops; mod scope; mod show; +mod str; +pub use self::str::*; pub use args::*; pub use array::*; pub use capture::*; @@ -35,6 +37,7 @@ pub use show::*; pub use styles::*; pub use value::*; +use parking_lot::{MappedRwLockWriteGuard, RwLockWriteGuard}; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult}; @@ -207,9 +210,9 @@ impl Eval for Expr { Self::Array(v) => v.eval(ctx, scp).map(Value::Array), Self::Dict(v) => v.eval(ctx, scp).map(Value::Dict), Self::Group(v) => v.eval(ctx, scp), - Self::Call(v) => v.eval(ctx, scp), + Self::FuncCall(v) => v.eval(ctx, scp), + Self::MethodCall(v) => v.eval(ctx, scp), Self::Closure(v) => v.eval(ctx, scp), - Self::With(v) => v.eval(ctx, scp), Self::Unary(v) => v.eval(ctx, scp), Self::Binary(v) => v.eval(ctx, scp), Self::Let(v) => v.eval(ctx, scp), @@ -254,7 +257,7 @@ impl Eval for Ident { fn eval(&self, _: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { match scp.get(self) { - Some(slot) => Ok(slot.read().unwrap().clone()), + Some(slot) => Ok(slot.read().clone()), None => bail!(self.span(), "unknown variable"), } } @@ -384,47 +387,62 @@ impl BinaryExpr { op: fn(Value, Value) -> StrResult<Value>, ) -> EvalResult<Value> { let rhs = self.rhs().eval(ctx, scp)?; - self.lhs().access( - ctx, - scp, - Box::new(|target| { - let lhs = std::mem::take(&mut *target); - *target = op(lhs, rhs).at(self.span())?; - Ok(()) - }), - )?; + let lhs = self.lhs(); + let mut location = lhs.access(ctx, scp)?; + let lhs = std::mem::take(&mut *location); + *location = op(lhs, rhs).at(self.span())?; Ok(Value::None) } } -impl Eval for CallExpr { +impl Eval for FuncCall { type Output = Value; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { - let span = self.callee().span(); let callee = self.callee().eval(ctx, scp)?; let args = self.args().eval(ctx, scp)?; Ok(match callee { Value::Array(array) => { - array.get(args.into_index()?).map(Value::clone).at(self.span()) + array.get(args.into_index()?).map(Value::clone).at(self.span())? } 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) => { let point = || Tracepoint::Call(func.name().map(ToString::to_string)); - func.call(ctx, args).trace(point, self.span()) + func.call(ctx, args).trace(point, self.span())? } v => bail!( - span, + self.callee().span(), "expected callable or collection, found {}", v.type_name(), ), - }?) + }) + } +} + +impl Eval for MethodCall { + type Output = Value; + + fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { + let span = self.span(); + let method = self.method(); + let point = || Tracepoint::Call(Some(method.to_string())); + + Ok(if Value::is_mutable_method(&method) { + let args = self.args().eval(ctx, scp)?; + let mut receiver = self.receiver().access(ctx, scp)?; + receiver.call_mut(ctx, &method, span, args).trace(point, span)?; + Value::None + } else { + let receiver = self.receiver().eval(ctx, scp)?; + let args = self.args().eval(ctx, scp)?; + receiver.call(ctx, &method, span, args).trace(point, span)? + }) } } @@ -527,17 +545,6 @@ impl Eval for ClosureExpr { } } -impl Eval for WithExpr { - type Output = Value; - - fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { - let callee = self.callee(); - let func = callee.eval(ctx, scp)?.cast::<Func>().at(callee.span())?; - let args = self.args().eval(ctx, scp)?; - Ok(Value::Func(func.with(args))) - } -} - impl Eval for LetExpr { type Output = Value; @@ -694,13 +701,13 @@ impl Eval for ImportExpr { match self.imports() { Imports::Wildcard => { for (var, slot) in module.scope.iter() { - scp.top.def_mut(var, slot.read().unwrap().clone()); + scp.top.def_mut(var, slot.read().clone()); } } Imports::Items(idents) => { for ident in idents { if let Some(slot) = module.scope.get(&ident) { - scp.top.def_mut(ident.take(), slot.read().unwrap().clone()); + scp.top.def_mut(ident.take(), slot.read().clone()); } else { bail!(ident.span(), "unresolved import"); } @@ -773,56 +780,85 @@ impl Eval for ReturnExpr { } } -/// Try to mutably access the value an expression points to. -/// -/// This only works if the expression is a valid lvalue. +/// Access an expression mutably. pub trait Access { - /// Try to access the value. - fn access(&self, ctx: &mut Context, scp: &mut Scopes, f: Handler) -> TypResult<()>; + /// Access the value. + fn access<'a>( + &self, + ctx: &mut Context, + scp: &'a mut Scopes, + ) -> EvalResult<Location<'a>>; } -/// Process an accessed value. -type Handler<'a> = Box<dyn FnOnce(&mut Value) -> TypResult<()> + 'a>; - impl Access for Expr { - fn access(&self, ctx: &mut Context, scp: &mut Scopes, f: Handler) -> TypResult<()> { + fn access<'a>( + &self, + ctx: &mut Context, + scp: &'a mut Scopes, + ) -> EvalResult<Location<'a>> { match self { - Expr::Ident(ident) => ident.access(ctx, scp, f), - Expr::Call(call) => call.access(ctx, scp, f), - _ => bail!(self.span(), "cannot access this expression mutably"), + Expr::Ident(ident) => ident.access(ctx, scp), + Expr::FuncCall(call) => call.access(ctx, scp), + _ => bail!(self.span(), "cannot mutate a temporary value"), } } } impl Access for Ident { - fn access(&self, _: &mut Context, scp: &mut Scopes, f: Handler) -> TypResult<()> { + fn access<'a>( + &self, + _: &mut Context, + scp: &'a mut Scopes, + ) -> EvalResult<Location<'a>> { match scp.get(self) { Some(slot) => match slot.try_write() { - Ok(mut guard) => f(&mut guard), - Err(_) => bail!(self.span(), "cannot mutate a constant"), + Some(guard) => Ok(RwLockWriteGuard::map(guard, |v| v)), + None => bail!(self.span(), "cannot mutate a constant"), }, None => bail!(self.span(), "unknown variable"), } } } -impl Access for CallExpr { - fn access(&self, ctx: &mut Context, scp: &mut Scopes, f: Handler) -> TypResult<()> { +impl Access for FuncCall { + fn access<'a>( + &self, + ctx: &mut Context, + scp: &'a mut Scopes, + ) -> EvalResult<Location<'a>> { let args = self.args().eval(ctx, scp)?; - self.callee().access( - ctx, - scp, - Box::new(|value| match value { + let guard = self.callee().access(ctx, scp)?; + try_map(guard, |value| { + Ok(match value { Value::Array(array) => { - f(array.get_mut(args.into_index()?).at(self.span())?) + array.get_mut(args.into_index()?).at(self.span())? } - Value::Dict(dict) => f(dict.get_mut(args.into_key()?)), + Value::Dict(dict) => dict.get_mut(args.into_key()?), v => bail!( self.callee().span(), "expected collection, found {}", v.type_name(), ), - }), - ) + }) + }) } } + +/// A mutable location. +type Location<'a> = MappedRwLockWriteGuard<'a, Value>; + +/// Map a reader-writer lock with a function. +fn try_map<F>(location: Location, f: F) -> EvalResult<Location> +where + F: FnOnce(&mut Value) -> EvalResult<&mut Value>, +{ + let mut error = None; + MappedRwLockWriteGuard::try_map(location, |value| match f(value) { + Ok(value) => Some(value), + Err(err) => { + error = Some(err); + None + } + }) + .map_err(|_| error.unwrap()) +} diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 6a8f5284..9b46e8f6 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,9 +1,8 @@ use std::cmp::Ordering; -use super::{Dynamic, Value}; +use super::{Dynamic, StrExt, Value}; use crate::diag::StrResult; use crate::geom::{Align, Spec, SpecAxis}; -use crate::util::EcoString; use Value::*; /// Bail with a type mismatch error. @@ -174,8 +173,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { (Fractional(a), Float(b)) => Fractional(a * b), (Int(a), Fractional(b)) => Fractional(a as f64 * b), - (Str(a), Int(b)) => Str(repeat_str(a, b)?), - (Int(a), Str(b)) => Str(repeat_str(b, a)?), + (Str(a), Int(b)) => Str(StrExt::repeat(&a, b)?), + (Int(a), Str(b)) => Str(StrExt::repeat(&b, a)?), (Array(a), Int(b)) => Array(a.repeat(b)?), (Int(a), Array(b)) => Array(b.repeat(a)?), (Content(a), Int(b)) => Content(a.repeat(b)?), @@ -185,16 +184,6 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { }) } -/// Repeat a string a number of times. -fn repeat_str(string: EcoString, n: i64) -> StrResult<EcoString> { - let n = usize::try_from(n) - .ok() - .and_then(|n| string.len().checked_mul(n).map(|_| n)) - .ok_or_else(|| format!("cannot repeat this string {} times", n))?; - - Ok(string.repeat(n)) -} - /// Compute the quotient of two values. pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> { Ok(match (lhs, rhs) { @@ -358,7 +347,7 @@ pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> { Some(match (lhs, rhs) { (Value::Str(a), Value::Str(b)) => b.contains(a.as_str()), - (Value::Str(a), Value::Dict(b)) => b.contains_key(a), + (Value::Str(a), Value::Dict(b)) => b.contains(a), (a, Value::Array(b)) => b.contains(a), _ => return Option::None, }) diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 19899cae..8acaa431 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -2,7 +2,9 @@ use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::iter; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; + +use parking_lot::RwLock; use super::{Args, Func, Node, Value}; use crate::diag::TypResult; @@ -113,7 +115,7 @@ impl Hash for Scope { self.values.len().hash(state); for (name, value) in self.values.iter() { name.hash(state); - value.read().unwrap().hash(state); + value.read().hash(state); } } } @@ -122,7 +124,7 @@ impl Debug for Scope { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Scope ")?; f.debug_map() - .entries(self.values.iter().map(|(k, v)| (k, v.read().unwrap()))) + .entries(self.values.iter().map(|(k, v)| (k, v.read()))) .finish() } } diff --git a/src/eval/str.rs b/src/eval/str.rs new file mode 100644 index 00000000..3b4349a1 --- /dev/null +++ b/src/eval/str.rs @@ -0,0 +1,37 @@ +use super::{Array, Value}; +use crate::diag::StrResult; +use crate::util::EcoString; + +/// Extra methods on strings. +pub trait StrExt { + /// Repeat a string a number of times. + fn repeat(&self, n: i64) -> StrResult<EcoString>; + + /// Split this string at whitespace or a specific pattern. + fn split(&self, at: Option<EcoString>) -> Array; +} + +impl StrExt for EcoString { + fn repeat(&self, n: i64) -> StrResult<EcoString> { + let n = usize::try_from(n) + .ok() + .and_then(|n| self.len().checked_mul(n).map(|_| n)) + .ok_or_else(|| format!("cannot repeat this string {} times", n))?; + + Ok(self.repeat(n)) + } + + fn split(&self, at: Option<EcoString>) -> Array { + if let Some(pat) = at { + self.as_str() + .split(pat.as_str()) + .map(|s| Value::Str(s.into())) + .collect() + } else { + self.as_str() + .split_whitespace() + .map(|s| Value::Str(s.into())) + .collect() + } + } +} diff --git a/src/eval/value.rs b/src/eval/value.rs index 0e0d08a8..a76b377d 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,10 +4,10 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{ops, Args, Array, Content, Dict, Func, Layout}; -use crate::diag::{with_alternative, StrResult}; +use super::{ops, Args, Array, Content, Context, Dict, Func, Layout, StrExt}; +use crate::diag::{with_alternative, At, StrResult, TypResult}; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; -use crate::syntax::Spanned; +use crate::syntax::{Span, Spanned}; use crate::util::EcoString; /// A computational value. @@ -120,6 +120,121 @@ impl Value { v => Content::Text(v.repr()).monospaced(), } } + + /// Call a method on the value. + pub fn call( + &self, + ctx: &mut Context, + method: &str, + span: Span, + mut args: Args, + ) -> TypResult<Self> { + let name = self.type_name(); + let missing = || Err(missing_method(name, method)).at(span); + + let output = match self { + Value::Str(string) => match method { + "len" => Value::Int(string.len() as i64), + "trim" => Value::Str(string.trim().into()), + "split" => Value::Array(string.split(args.eat()?)), + _ => missing()?, + }, + + Value::Array(array) => match method { + "len" => Value::Int(array.len()), + "slice" => { + let start = args.expect("start")?; + let mut end = args.eat()?; + if end.is_none() { + end = args.named("count")?.map(|c: i64| start + c); + } + Value::Array(array.slice(start, end).at(span)?) + } + "map" => Value::Array(array.map(ctx, args.expect("function")?)?), + "filter" => Value::Array(array.filter(ctx, args.expect("function")?)?), + "flatten" => Value::Array(array.flatten()), + "find" => { + array.find(args.expect("value")?).map_or(Value::None, Value::Int) + } + "join" => { + let sep = args.eat()?; + let last = args.named("last")?; + array.join(sep, last).at(span)? + } + "sorted" => Value::Array(array.sorted().at(span)?), + _ => missing()?, + }, + + Value::Dict(dict) => match method { + "len" => Value::Int(dict.len()), + "keys" => Value::Array(dict.keys()), + "values" => Value::Array(dict.values()), + "pairs" => Value::Array(dict.map(ctx, args.expect("function")?)?), + _ => missing()?, + }, + + Value::Func(func) => match method { + "with" => Value::Func(func.clone().with(args.take())), + _ => missing()?, + }, + + Value::Args(args) => match method { + "positional" => Value::Array(args.to_positional()), + "named" => Value::Dict(args.to_named()), + _ => missing()?, + }, + + _ => missing()?, + }; + + args.finish()?; + Ok(output) + } + + /// Call a mutating method on the value. + pub fn call_mut( + &mut self, + _: &mut Context, + method: &str, + span: Span, + mut args: Args, + ) -> TypResult<()> { + let name = self.type_name(); + let missing = || Err(missing_method(name, method)).at(span); + + match self { + Value::Array(array) => match method { + "push" => array.push(args.expect("value")?), + "pop" => array.pop().at(span)?, + "insert" => { + array.insert(args.expect("index")?, args.expect("value")?).at(span)? + } + "remove" => array.remove(args.expect("index")?).at(span)?, + _ => missing()?, + }, + + Value::Dict(dict) => match method { + "remove" => dict.remove(args.expect("key")?).at(span)?, + _ => missing()?, + }, + + _ => missing()?, + } + + args.finish()?; + Ok(()) + } + + /// Whether a specific method is mutable. + pub fn is_mutable_method(method: &str) -> bool { + matches!(method, "push" | "pop" | "insert" | "remove") + } +} + +/// The missing method error message. +#[cold] +fn missing_method(type_name: &str, method: &str) -> String { + format!("type {type_name} has no method `{method}`") } impl Default for Value { diff --git a/src/geom/angle.rs b/src/geom/angle.rs index b4d6f79a..b64ec77e 100644 --- a/src/geom/angle.rs +++ b/src/geom/angle.rs @@ -129,7 +129,7 @@ assign_impl!(Angle /= f64); impl Sum for Angle { fn sum<I: Iterator<Item = Angle>>(iter: I) -> Self { - iter.fold(Angle::zero(), Add::add) + Self(iter.map(|s| s.0).sum()) } } /// Different units of angular measurement. diff --git a/src/library/mod.rs b/src/library/mod.rs index 087ff7ea..528a2ce7 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -67,13 +67,10 @@ pub fn new() -> Scope { std.def_node::<math::MathNode>("math"); // Utility functions. - std.def_fn("assert", utility::assert); std.def_fn("type", utility::type_); - std.def_fn("repr", utility::repr); - std.def_fn("join", utility::join); + std.def_fn("assert", utility::assert); std.def_fn("int", utility::int); std.def_fn("float", utility::float); - std.def_fn("str", utility::str); std.def_fn("abs", utility::abs); std.def_fn("min", utility::min); std.def_fn("max", utility::max); @@ -83,13 +80,13 @@ pub fn new() -> Scope { std.def_fn("range", utility::range); std.def_fn("rgb", utility::rgb); std.def_fn("cmyk", utility::cmyk); + std.def_fn("repr", utility::repr); + std.def_fn("str", utility::str); std.def_fn("lower", utility::lower); std.def_fn("upper", utility::upper); std.def_fn("letter", utility::letter); std.def_fn("roman", utility::roman); std.def_fn("symbol", utility::symbol); - std.def_fn("len", utility::len); - std.def_fn("sorted", utility::sorted); // Predefined colors. std.def_const("black", Color::BLACK); diff --git a/src/library/utility/color.rs b/src/library/utility/color.rs new file mode 100644 index 00000000..df24f615 --- /dev/null +++ b/src/library/utility/color.rs @@ -0,0 +1,58 @@ +use std::str::FromStr; + +use crate::library::prelude::*; + +/// Create an RGB(A) color. +pub fn rgb(_: &mut Context, args: &mut Args) -> TypResult<Value> { + Ok(Value::from( + if let Some(string) = args.find::<Spanned<EcoString>>()? { + match RgbaColor::from_str(&string.v) { + Ok(color) => color, + Err(_) => bail!(string.span, "invalid hex string"), + } + } else { + struct Component(u8); + + castable! { + Component, + Expected: "integer or relative", + Value::Int(v) => match v { + 0 ..= 255 => Self(v as u8), + _ => Err("must be between 0 and 255")?, + }, + Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) { + Self((v.get() * 255.0).round() as u8) + } else { + Err("must be between 0% and 100%")? + }, + } + + let Component(r) = args.expect("red component")?; + let Component(g) = args.expect("green component")?; + let Component(b) = args.expect("blue component")?; + let Component(a) = args.eat()?.unwrap_or(Component(255)); + RgbaColor::new(r, g, b, a) + }, + )) +} + +/// Create a CMYK color. +pub fn cmyk(_: &mut Context, args: &mut Args) -> TypResult<Value> { + struct Component(u8); + + castable! { + Component, + Expected: "relative", + Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) { + Self((v.get() * 255.0).round() as u8) + } else { + Err("must be between 0% and 100%")? + }, + } + + let Component(c) = args.expect("cyan component")?; + let Component(m) = args.expect("magenta component")?; + let Component(y) = args.expect("yellow component")?; + let Component(k) = args.expect("key component")?; + Ok(Value::Color(CmykColor::new(c, m, y, k).into())) +} diff --git a/src/library/utility/math.rs b/src/library/utility/math.rs index e48af426..0aebc573 100644 --- a/src/library/utility/math.rs +++ b/src/library/utility/math.rs @@ -2,6 +2,35 @@ use std::cmp::Ordering; use crate::library::prelude::*; +/// Convert a value to a integer. +pub fn int(_: &mut Context, args: &mut Args) -> TypResult<Value> { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Int(match v { + Value::Bool(v) => v as i64, + Value::Int(v) => v, + Value::Float(v) => v as i64, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid integer"), + }, + v => bail!(span, "cannot convert {} to integer", v.type_name()), + })) +} + +/// Convert a value to a float. +pub fn float(_: &mut Context, args: &mut Args) -> TypResult<Value> { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Float(match v { + Value::Int(v) => v as f64, + Value::Float(v) => v, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid float"), + }, + v => bail!(span, "cannot convert {} to float", v.type_name()), + })) +} + /// The absolute value of a numeric value. pub fn abs(_: &mut Context, args: &mut Args) -> TypResult<Value> { let Spanned { v, span } = args.expect("numeric value")?; diff --git a/src/library/utility/mod.rs b/src/library/utility/mod.rs index d85c3f12..13220242 100644 --- a/src/library/utility/mod.rs +++ b/src/library/utility/mod.rs @@ -1,15 +1,19 @@ //! Computational utility functions. +mod color; mod math; -mod numbering; +mod string; +pub use color::*; pub use math::*; -pub use numbering::*; - -use std::str::FromStr; +pub use string::*; use crate::library::prelude::*; -use crate::library::text::{Case, TextNode}; + +/// The name of a value's type. +pub fn type_(_: &mut Context, args: &mut Args) -> TypResult<Value> { + Ok(args.expect::<Value>("value")?.type_name().into()) +} /// Ensure that a condition is fulfilled. pub fn assert(_: &mut Context, args: &mut Args) -> TypResult<Value> { @@ -19,169 +23,3 @@ pub fn assert(_: &mut Context, args: &mut Args) -> TypResult<Value> { } Ok(Value::None) } - -/// The name of a value's type. -pub fn type_(_: &mut Context, args: &mut Args) -> TypResult<Value> { - Ok(args.expect::<Value>("value")?.type_name().into()) -} - -/// The string representation of a value. -pub fn repr(_: &mut Context, args: &mut Args) -> TypResult<Value> { - Ok(args.expect::<Value>("value")?.repr().into()) -} - -/// Join a sequence of values, optionally interspersing it with another value. -pub fn join(_: &mut Context, args: &mut Args) -> TypResult<Value> { - let span = args.span; - let sep = args.named::<Value>("sep")?.unwrap_or(Value::None); - - let mut result = Value::None; - let mut iter = args.all::<Value>()?.into_iter(); - - if let Some(first) = iter.next() { - result = first; - } - - for value in iter { - result = result.join(sep.clone()).at(span)?; - result = result.join(value).at(span)?; - } - - Ok(result) -} - -/// Convert a value to a integer. -pub fn int(_: &mut Context, args: &mut Args) -> TypResult<Value> { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Int(match v { - Value::Bool(v) => v as i64, - Value::Int(v) => v, - Value::Float(v) => v as i64, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid integer"), - }, - v => bail!(span, "cannot convert {} to integer", v.type_name()), - })) -} - -/// Convert a value to a float. -pub fn float(_: &mut Context, args: &mut Args) -> TypResult<Value> { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Float(match v { - Value::Int(v) => v as f64, - Value::Float(v) => v, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid float"), - }, - v => bail!(span, "cannot convert {} to float", v.type_name()), - })) -} - -/// Cconvert a value to a string. -pub fn str(_: &mut Context, args: &mut Args) -> TypResult<Value> { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Str(match v { - Value::Int(v) => format_eco!("{}", v), - Value::Float(v) => format_eco!("{}", v), - Value::Str(v) => v, - v => bail!(span, "cannot convert {} to string", v.type_name()), - })) -} - -/// Create an RGB(A) color. -pub fn rgb(_: &mut Context, args: &mut Args) -> TypResult<Value> { - Ok(Value::from( - if let Some(string) = args.find::<Spanned<EcoString>>()? { - match RgbaColor::from_str(&string.v) { - Ok(color) => color, - Err(_) => bail!(string.span, "invalid hex string"), - } - } else { - struct Component(u8); - - castable! { - Component, - Expected: "integer or relative", - Value::Int(v) => match v { - 0 ..= 255 => Self(v as u8), - _ => Err("must be between 0 and 255")?, - }, - Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) { - Self((v.get() * 255.0).round() as u8) - } else { - Err("must be between 0% and 100%")? - }, - } - - let Component(r) = args.expect("red component")?; - let Component(g) = args.expect("green component")?; - let Component(b) = args.expect("blue component")?; - let Component(a) = args.eat()?.unwrap_or(Component(255)); - RgbaColor::new(r, g, b, a) - }, - )) -} - -/// Create a CMYK color. -pub fn cmyk(_: &mut Context, args: &mut Args) -> TypResult<Value> { - struct Component(u8); - - castable! { - Component, - Expected: "relative", - Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) { - Self((v.get() * 255.0).round() as u8) - } else { - Err("must be between 0% and 100%")? - }, - } - - let Component(c) = args.expect("cyan component")?; - let Component(m) = args.expect("magenta component")?; - let Component(y) = args.expect("yellow component")?; - let Component(k) = args.expect("key component")?; - Ok(Value::Color(CmykColor::new(c, m, y, k).into())) -} - -/// The length of a string, an array or a dictionary. -pub fn len(_: &mut Context, args: &mut Args) -> TypResult<Value> { - let Spanned { v, span } = args.expect("collection")?; - Ok(Value::Int(match v { - Value::Str(v) => v.len() as i64, - Value::Array(v) => v.len(), - Value::Dict(v) => v.len(), - v => bail!( - span, - "expected string, array or dictionary, found {}", - v.type_name(), - ), - })) -} - -/// Convert a string to lowercase. -pub fn lower(_: &mut Context, args: &mut Args) -> TypResult<Value> { - case(Case::Lower, args) -} - -/// Convert a string to uppercase. -pub fn upper(_: &mut Context, args: &mut Args) -> TypResult<Value> { - case(Case::Upper, args) -} - -/// Change the case of a string or content. -fn case(case: Case, args: &mut Args) -> TypResult<Value> { - let Spanned { v, span } = args.expect("string or content")?; - Ok(match v { - Value::Str(v) => Value::Str(case.apply(&v).into()), - Value::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))), - v => bail!(span, "expected string or content, found {}", v.type_name()), - }) -} - -/// The sorted version of an array. -pub fn sorted(_: &mut Context, args: &mut Args) -> TypResult<Value> { - let Spanned { v, span } = args.expect::<Spanned<Array>>("array")?; - Ok(Value::Array(v.sorted().at(span)?)) -} diff --git a/src/library/utility/numbering.rs b/src/library/utility/string.rs index 0070873f..92d80be2 100644 --- a/src/library/utility/numbering.rs +++ b/src/library/utility/string.rs @@ -1,4 +1,41 @@ use crate::library::prelude::*; +use crate::library::text::{Case, TextNode}; + +/// The string representation of a value. +pub fn repr(_: &mut Context, args: &mut Args) -> TypResult<Value> { + Ok(args.expect::<Value>("value")?.repr().into()) +} + +/// Cconvert a value to a string. +pub fn str(_: &mut Context, args: &mut Args) -> TypResult<Value> { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Str(match v { + Value::Int(v) => format_eco!("{}", v), + Value::Float(v) => format_eco!("{}", v), + Value::Str(v) => v, + v => bail!(span, "cannot convert {} to string", v.type_name()), + })) +} + +/// Convert a string to lowercase. +pub fn lower(_: &mut Context, args: &mut Args) -> TypResult<Value> { + case(Case::Lower, args) +} + +/// Convert a string to uppercase. +pub fn upper(_: &mut Context, args: &mut Args) -> TypResult<Value> { + case(Case::Upper, args) +} + +/// Change the case of a string or content. +fn case(case: Case, args: &mut Args) -> TypResult<Value> { + let Spanned { v, span } = args.expect("string or content")?; + Ok(match v { + Value::Str(v) => Value::Str(case.apply(&v).into()), + Value::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))), + v => bail!(span, "expected string or content, found {}", v.type_name()), + }) +} /// Converts an integer into one or multiple letters. pub fn letter(_: &mut Context, args: &mut Args) -> TypResult<Value> { diff --git a/src/parse/incremental.rs b/src/parse/incremental.rs index 468f344e..a2ba502b 100644 --- a/src/parse/incremental.rs +++ b/src/parse/incremental.rs @@ -4,7 +4,8 @@ use std::sync::Arc; use crate::syntax::{Green, GreenNode, NodeKind}; use super::{ - is_newline, parse, reparse_block, reparse_content, reparse_markup_elements, TokenMode, + is_newline, parse, reparse_code_block, reparse_content_block, + reparse_markup_elements, TokenMode, }; /// Allows partial refreshs of the [`Green`] node tree. @@ -210,12 +211,12 @@ impl Reparser<'_> { } let (newborns, terminated, amount) = match mode { - ReparseMode::Code => reparse_block( + ReparseMode::Code => reparse_code_block( &prefix, &self.src[newborn_span.start ..], newborn_span.len(), ), - ReparseMode::Content => reparse_content( + ReparseMode::Content => reparse_content_block( &prefix, &self.src[newborn_span.start ..], newborn_span.len(), @@ -344,7 +345,6 @@ mod tests { test("this~is -- in my opinion -- spectacular", 8 .. 10, "---", 5 .. 25); test("understanding `code` is complicated", 15 .. 15, "C ", 14 .. 22); test("{ let x = g() }", 10 .. 12, "f(54", 0 .. 17); - test("a #let rect with (fill: eastern)\nb", 16 .. 31, " (stroke: conifer", 2 .. 34); test(r#"a ```typst hello``` b"#, 16 .. 17, "", 2 .. 18); test(r#"a ```typst hello```"#, 16 .. 17, "", 2 .. 18); test("#for", 4 .. 4, "//", 0 .. 6); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 5eaba8b0..58b81521 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -31,7 +31,7 @@ pub fn parse(src: &str) -> Arc<GreenNode> { /// Reparse a code block. /// /// Returns `Some` if all of the input was consumed. -pub fn reparse_block( +pub fn reparse_code_block( prefix: &str, src: &str, end_pos: usize, @@ -41,7 +41,7 @@ pub fn reparse_block( return None; } - block(&mut p); + code_block(&mut p); let (mut green, terminated) = p.consume()?; let first = green.remove(0); @@ -55,7 +55,7 @@ pub fn reparse_block( /// Reparse a content block. /// /// Returns `Some` if all of the input was consumed. -pub fn reparse_content( +pub fn reparse_content_block( prefix: &str, src: &str, end_pos: usize, @@ -65,7 +65,7 @@ pub fn reparse_content( return None; } - content(&mut p); + content_block(&mut p); let (mut green, terminated) = p.consume()?; let first = green.remove(0); @@ -236,8 +236,8 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { | NodeKind::Include => markup_expr(p), // Code and content block. - NodeKind::LeftBrace => block(p), - NodeKind::LeftBracket => content(p), + NodeKind::LeftBrace => code_block(p), + NodeKind::LeftBracket => content_block(p), NodeKind::Error(_, _) => p.eat(), _ => p.unexpected(), @@ -364,7 +364,7 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult { // Exclamation mark, parenthesis or bracket means this is a function // call. if let Some(NodeKind::LeftParen | NodeKind::LeftBracket) = p.peek_direct() { - call(p, marker)?; + func_call(p, marker)?; continue; } @@ -372,8 +372,9 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult { break; } - if p.at(&NodeKind::With) { - with_expr(p, marker)?; + if p.at(&NodeKind::Dot) { + method_call(p, marker)?; + continue; } let op = if p.eat_if(&NodeKind::Not) { @@ -432,8 +433,8 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult { // Structures. Some(NodeKind::LeftParen) => parenthesized(p, atomic), - Some(NodeKind::LeftBrace) => Ok(block(p)), - Some(NodeKind::LeftBracket) => Ok(content(p)), + Some(NodeKind::LeftBrace) => Ok(code_block(p)), + Some(NodeKind::LeftBracket) => Ok(content_block(p)), // Keywords. Some(NodeKind::Let) => let_expr(p), @@ -671,7 +672,7 @@ fn params(p: &mut Parser, marker: Marker) { } /// Parse a code block: `{...}`. -fn block(p: &mut Parser) { +fn code_block(p: &mut Parser) { p.perform(NodeKind::CodeBlock, |p| { p.start_group(Group::Brace); while !p.eof() { @@ -689,7 +690,7 @@ fn block(p: &mut Parser) { } // Parse a content block: `[...]`. -fn content(p: &mut Parser) { +fn content_block(p: &mut Parser) { p.perform(NodeKind::ContentBlock, |p| { p.start_group(Group::Bracket); markup(p, true); @@ -698,8 +699,17 @@ fn content(p: &mut Parser) { } /// Parse a function call. -fn call(p: &mut Parser, callee: Marker) -> ParseResult { - callee.perform(p, NodeKind::CallExpr, |p| args(p, true, true)) +fn func_call(p: &mut Parser, callee: Marker) -> ParseResult { + callee.perform(p, NodeKind::FuncCall, |p| args(p, true, true)) +} + +/// Parse a method call. +fn method_call(p: &mut Parser, marker: Marker) -> ParseResult { + marker.perform(p, NodeKind::MethodCall, |p| { + p.eat_assert(&NodeKind::Dot); + ident(p)?; + args(p, true, true) + }) } /// Parse the arguments to a function call. @@ -721,21 +731,13 @@ fn args(p: &mut Parser, direct: bool, brackets: bool) -> ParseResult { } while brackets && p.peek_direct() == Some(&NodeKind::LeftBracket) { - content(p); + content_block(p); } }); Ok(()) } -/// Parse a with expression. -fn with_expr(p: &mut Parser, marker: Marker) -> ParseResult { - marker.perform(p, NodeKind::WithExpr, |p| { - p.eat_assert(&NodeKind::With); - args(p, false, false) - }) -} - /// Parse a let expression. fn let_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::LetExpr, |p| { @@ -744,30 +746,26 @@ fn let_expr(p: &mut Parser) -> ParseResult { let marker = p.marker(); ident(p)?; - if p.at(&NodeKind::With) { - with_expr(p, marker)?; - } else { - // If a parenthesis follows, this is a function definition. - let has_params = p.peek_direct() == Some(&NodeKind::LeftParen); - if has_params { - let marker = p.marker(); - p.start_group(Group::Paren); - collection(p); - p.end_group(); - params(p, marker); - } + // If a parenthesis follows, this is a function definition. + let has_params = p.peek_direct() == Some(&NodeKind::LeftParen); + if has_params { + let marker = p.marker(); + p.start_group(Group::Paren); + collection(p); + p.end_group(); + params(p, marker); + } - if p.eat_if(&NodeKind::Eq) { - expr(p)?; - } else if has_params { - // Function definitions must have a body. - p.expected("body"); - } + if p.eat_if(&NodeKind::Eq) { + expr(p)?; + } else if has_params { + // Function definitions must have a body. + p.expected("body"); + } - // Rewrite into a closure expression if it's a function definition. - if has_params { - marker.end(p, NodeKind::ClosureExpr); - } + // Rewrite into a closure expression if it's a function definition. + if has_params { + marker.end(p, NodeKind::ClosureExpr); } Ok(()) @@ -931,8 +929,8 @@ fn return_expr(p: &mut Parser) -> ParseResult { /// Parse a control flow body. fn body(p: &mut Parser) -> ParseResult { match p.peek() { - Some(NodeKind::LeftBracket) => Ok(content(p)), - Some(NodeKind::LeftBrace) => Ok(block(p)), + Some(NodeKind::LeftBracket) => Ok(content_block(p)), + Some(NodeKind::LeftBrace) => Ok(code_block(p)), _ => { p.expected("body"); Err(ParseError) diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 33cf489c..63ba4918 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -352,7 +352,16 @@ impl<'s> Parser<'s> { match self.groups.last().map(|group| group.kind) { Some(Group::Strong | Group::Emph) => n >= 2, - Some(Group::Expr | Group::Imports) => n >= 1, + Some(Group::Imports) => n >= 1, + Some(Group::Expr) if n >= 1 => { + // Allow else and method call to continue on next line. + self.groups.iter().nth_back(1).map(|group| group.kind) + != Some(Group::Brace) + || !matches!( + self.tokens.clone().next(), + Some(NodeKind::Else | NodeKind::Dot) + ) + } _ => false, } } diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 752714fd..0c05d770 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -10,6 +10,7 @@ use crate::syntax::{ErrorPos, NodeKind}; use crate::util::EcoString; /// An iterator over the tokens of a string of source code. +#[derive(Clone)] pub struct Tokens<'s> { /// The underlying scanner. s: Scanner<'s>, @@ -184,6 +185,7 @@ impl<'s> Tokens<'s> { '=' => NodeKind::Eq, '<' => NodeKind::Lt, '>' => NodeKind::Gt, + '.' if self.s.check_or(true, |n| !n.is_ascii_digit()) => NodeKind::Dot, // Identifiers. c if is_id_start(c) => self.ident(start), @@ -572,7 +574,6 @@ fn keyword(ident: &str) -> Option<NodeKind> { "not" => NodeKind::Not, "and" => NodeKind::And, "or" => NodeKind::Or, - "with" => NodeKind::With, "let" => NodeKind::Let, "set" => NodeKind::Set, "show" => NodeKind::Show, @@ -859,6 +860,7 @@ mod tests { t!(Code: "-" => Minus); t!(Code[" a1"]: "*" => Star); t!(Code[" a1"]: "/" => Slash); + t!(Code[" a/"]: "." => Dot); t!(Code: "=" => Eq); t!(Code: "==" => EqEq); t!(Code: "!=" => ExclEq); @@ -875,7 +877,7 @@ mod tests { // Test combinations. t!(Code: "<=>" => LtEq, Gt); - t!(Code[" a/"]: "..." => Dots, Invalid(".")); + t!(Code[" a/"]: "..." => Dots, Dot); // Test hyphen as symbol vs part of identifier. t!(Code[" /"]: "-1" => Minus, Int(1)); diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index b8780590..cb0a99b9 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -234,11 +234,11 @@ pub enum Expr { /// A binary operation: `a + b`. Binary(BinaryExpr), /// An invocation of a function: `f(x, y)`. - Call(CallExpr), + FuncCall(FuncCall), + /// An invocation of a method: `array.push(v)`. + MethodCall(MethodCall), /// A closure expression: `(x, y) => z`. Closure(ClosureExpr), - /// A with expression: `f with (x, y: 1)`. - With(WithExpr), /// A let expression: `let x = 1`. Let(LetExpr), /// A set expression: `set text(...)`. @@ -276,9 +276,9 @@ impl TypedNode for Expr { NodeKind::DictExpr => node.cast().map(Self::Dict), NodeKind::UnaryExpr => node.cast().map(Self::Unary), NodeKind::BinaryExpr => node.cast().map(Self::Binary), - NodeKind::CallExpr => node.cast().map(Self::Call), + NodeKind::FuncCall => node.cast().map(Self::FuncCall), + NodeKind::MethodCall => node.cast().map(Self::MethodCall), NodeKind::ClosureExpr => node.cast().map(Self::Closure), - NodeKind::WithExpr => node.cast().map(Self::With), NodeKind::LetExpr => node.cast().map(Self::Let), NodeKind::SetExpr => node.cast().map(Self::Set), NodeKind::ShowExpr => node.cast().map(Self::Show), @@ -306,9 +306,9 @@ impl TypedNode for Expr { Self::Group(v) => v.as_red(), Self::Unary(v) => v.as_red(), Self::Binary(v) => v.as_red(), - Self::Call(v) => v.as_red(), + Self::FuncCall(v) => v.as_red(), + Self::MethodCall(v) => v.as_red(), Self::Closure(v) => v.as_red(), - Self::With(v) => v.as_red(), Self::Let(v) => v.as_red(), Self::Set(v) => v.as_red(), Self::Show(v) => v.as_red(), @@ -331,7 +331,7 @@ impl Expr { matches!( self, Self::Ident(_) - | Self::Call(_) + | Self::FuncCall(_) | Self::Let(_) | Self::Set(_) | Self::Show(_) @@ -735,19 +735,45 @@ pub enum Associativity { } node! { - /// An invocation of a function: `foo(...)`. - CallExpr: CallExpr + /// An invocation of a function: `f(x, y)`. + FuncCall: FuncCall } -impl CallExpr { +impl FuncCall { /// The function to call. pub fn callee(&self) -> Expr { - self.0.cast_first_child().expect("call is missing callee") + self.0.cast_first_child().expect("function call is missing callee") } /// The arguments to the function. pub fn args(&self) -> CallArgs { - self.0.cast_last_child().expect("call is missing argument list") + self.0 + .cast_last_child() + .expect("function call is missing argument list") + } +} + +node! { + /// An invocation of a method: `array.push(v)`. + MethodCall: MethodCall +} + +impl MethodCall { + /// The value to call the method on. + pub fn receiver(&self) -> Expr { + self.0.cast_first_child().expect("method call is missing callee") + } + + /// The name of the method. + pub fn method(&self) -> Ident { + self.0.cast_last_child().expect("method call is missing name") + } + + /// The arguments to the method. + pub fn args(&self) -> CallArgs { + self.0 + .cast_last_child() + .expect("method call is missing argument list") } } @@ -863,25 +889,6 @@ impl TypedNode for ClosureParam { } node! { - /// A with expression: `f with (x, y: 1)`. - WithExpr -} - -impl WithExpr { - /// The function to apply the arguments to. - pub fn callee(&self) -> Expr { - self.0.cast_first_child().expect("with expression is missing callee") - } - - /// The arguments to apply to the function. - pub fn args(&self) -> CallArgs { - self.0 - .cast_first_child() - .expect("with expression is missing argument list") - } -} - -node! { /// A let expression: `let x = 1`. LetExpr } @@ -891,10 +898,6 @@ impl LetExpr { pub fn binding(&self) -> Ident { match self.0.cast_first_child() { Some(Expr::Ident(binding)) => binding, - Some(Expr::With(with)) => match with.callee() { - Expr::Ident(binding) => binding, - _ => panic!("let .. with callee must be identifier"), - }, Some(Expr::Closure(closure)) => { closure.name().expect("let-bound closure is missing name") } diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index c0e3376e..bad434b9 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -11,10 +11,10 @@ pub fn highlight<F>(node: RedRef, range: Range<usize>, f: &mut F) where F: FnMut(Range<usize>, Category), { - for child in node.children() { + for (i, child) in node.children().enumerate() { let span = child.span(); if range.start <= span.end && range.end >= span.start { - if let Some(category) = Category::determine(child, node) { + if let Some(category) = Category::determine(child, node, i) { f(span.to_range(), category); } highlight(child, range.clone(), f); @@ -44,9 +44,9 @@ fn highlight_syntect_impl<F>( return; } - for child in node.children() { + for (i, child) in node.children().enumerate() { let mut scopes = scopes.clone(); - if let Some(category) = Category::determine(child, node) { + if let Some(category) = Category::determine(child, node, i) { scopes.push(Scope::new(category.tm_scope()).unwrap()) } highlight_syntect_impl(child, scopes, highlighter, f); @@ -101,8 +101,9 @@ pub enum Category { } impl Category { - /// Determine the highlighting category of a node given its parent. - pub fn determine(child: RedRef, parent: RedRef) -> Option<Category> { + /// Determine the highlighting category of a node given its parent and its + /// index in its siblings. + pub fn determine(child: RedRef, parent: RedRef, i: usize) -> Option<Category> { match child.kind() { NodeKind::LeftBrace => Some(Category::Bracket), NodeKind::RightBrace => Some(Category::Bracket), @@ -133,7 +134,6 @@ impl Category { NodeKind::Not => Some(Category::Keyword), NodeKind::And => Some(Category::Keyword), NodeKind::Or => Some(Category::Keyword), - NodeKind::With => Some(Category::Keyword), NodeKind::Let => Some(Category::Keyword), NodeKind::Set => Some(Category::Keyword), NodeKind::Show => Some(Category::Keyword), @@ -156,6 +156,7 @@ impl Category { _ => Some(Category::Operator), }, NodeKind::Slash => Some(Category::Operator), + NodeKind::Dot => Some(Category::Operator), NodeKind::PlusEq => Some(Category::Operator), NodeKind::HyphEq => Some(Category::Operator), NodeKind::StarEq => Some(Category::Operator), @@ -176,13 +177,11 @@ impl Category { NodeKind::Auto => Some(Category::Auto), NodeKind::Ident(_) => match parent.kind() { NodeKind::Named => None, - NodeKind::ClosureExpr if child.span().start == parent.span().start => { - Some(Category::Function) - } - NodeKind::WithExpr => Some(Category::Function), + NodeKind::ClosureExpr if i == 0 => Some(Category::Function), NodeKind::SetExpr => Some(Category::Function), NodeKind::ShowExpr => Some(Category::Function), - NodeKind::CallExpr => Some(Category::Function), + NodeKind::FuncCall => Some(Category::Function), + NodeKind::MethodCall if i > 0 => Some(Category::Function), _ => Some(Category::Variable), }, NodeKind::Bool(_) => Some(Category::Bool), @@ -210,12 +209,12 @@ impl Category { NodeKind::Named => None, NodeKind::UnaryExpr => None, NodeKind::BinaryExpr => None, - NodeKind::CallExpr => None, + NodeKind::FuncCall => None, + NodeKind::MethodCall => None, NodeKind::CallArgs => None, NodeKind::Spread => None, NodeKind::ClosureExpr => None, NodeKind::ClosureParams => None, - NodeKind::WithExpr => None, NodeKind::LetExpr => None, NodeKind::SetExpr => None, NodeKind::ShowExpr => None, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index e15cfabc..d0920d20 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -509,6 +509,8 @@ pub enum NodeKind { Minus, /// A slash: `/`. Slash, + /// A dot: `.`. + Dot, /// A single equals sign: `=`. Eq, /// Two equals signs: `==`. @@ -537,8 +539,6 @@ pub enum NodeKind { And, /// The `or` operator. Or, - /// The `with` operator. - With, /// Two dots: `..`. Dots, /// An equals sign followed by a greater-than sign: `=>`. @@ -659,7 +659,9 @@ pub enum NodeKind { /// A binary operation: `a + b`. BinaryExpr, /// An invocation of a function: `f(x, y)`. - CallExpr, + FuncCall, + /// An invocation of a method: `array.push(v)`. + MethodCall, /// A function call's argument list: `(x, y)`. CallArgs, /// Spreaded arguments or a parameter sink: `..x`. @@ -668,8 +670,6 @@ pub enum NodeKind { ClosureExpr, /// A closure's parameters: `(x, y)`. ClosureParams, - /// A with expression: `f with (x, y: 1)`. - WithExpr, /// A let expression: `let x = 1`. LetExpr, /// A set expression: `set text(...)`. @@ -802,7 +802,7 @@ impl NodeKind { | Self::WhileExpr | Self::ForExpr | Self::ImportExpr - | Self::CallExpr + | Self::FuncCall | Self::IncludeExpr | Self::LineComment | Self::BlockComment @@ -830,6 +830,7 @@ impl NodeKind { Self::Plus => "plus", Self::Minus => "minus", Self::Slash => "slash", + Self::Dot => "dot", Self::Eq => "assignment operator", Self::EqEq => "equality operator", Self::ExclEq => "inequality operator", @@ -844,7 +845,6 @@ impl NodeKind { Self::Not => "operator `not`", Self::And => "operator `and`", Self::Or => "operator `or`", - Self::With => "operator `with`", Self::Dots => "dots", Self::Arrow => "arrow", Self::None => "`none`", @@ -899,12 +899,12 @@ impl NodeKind { Self::Named => "named argument", Self::UnaryExpr => "unary expression", Self::BinaryExpr => "binary expression", - Self::CallExpr => "call", + Self::FuncCall => "function call", + Self::MethodCall => "method call", Self::CallArgs => "call arguments", Self::Spread => "parameter sink", Self::ClosureExpr => "closure", Self::ClosureParams => "closure parameters", - Self::WithExpr => "`with` expression", Self::LetExpr => "`let` expression", Self::SetExpr => "`set` expression", Self::ShowExpr => "`show` expression", @@ -954,6 +954,7 @@ impl Hash for NodeKind { Self::Plus => {} Self::Minus => {} Self::Slash => {} + Self::Dot => {} Self::Eq => {} Self::EqEq => {} Self::ExclEq => {} @@ -968,7 +969,6 @@ impl Hash for NodeKind { Self::Not => {} Self::And => {} Self::Or => {} - Self::With => {} Self::Dots => {} Self::Arrow => {} Self::None => {} @@ -1023,12 +1023,12 @@ impl Hash for NodeKind { Self::Named => {} Self::UnaryExpr => {} Self::BinaryExpr => {} - Self::CallExpr => {} + Self::FuncCall => {} + Self::MethodCall => {} Self::CallArgs => {} Self::Spread => {} Self::ClosureExpr => {} Self::ClosureParams => {} - Self::WithExpr => {} Self::LetExpr => {} Self::SetExpr => {} Self::ShowExpr => {} |
