summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/args.rs19
-rw-r--r--src/eval/array.rs145
-rw-r--r--src/eval/dict.rs43
-rw-r--r--src/eval/mod.rs150
-rw-r--r--src/eval/ops.rs19
-rw-r--r--src/eval/scope.rs8
-rw-r--r--src/eval/str.rs37
-rw-r--r--src/eval/value.rs121
-rw-r--r--src/geom/angle.rs2
-rw-r--r--src/library/mod.rs9
-rw-r--r--src/library/utility/color.rs58
-rw-r--r--src/library/utility/math.rs29
-rw-r--r--src/library/utility/mod.rs180
-rw-r--r--src/library/utility/string.rs (renamed from src/library/utility/numbering.rs)37
-rw-r--r--src/parse/incremental.rs8
-rw-r--r--src/parse/mod.rs94
-rw-r--r--src/parse/parser.rs11
-rw-r--r--src/parse/tokens.rs6
-rw-r--r--src/syntax/ast.rs75
-rw-r--r--src/syntax/highlight.rs27
-rw-r--r--src/syntax/mod.rs24
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 => {}