Methods
This commit is contained in:
parent
77d153d315
commit
beca01c826
82
Cargo.lock
generated
82
Cargo.lock
generated
@ -334,6 +334,15 @@ dependencies = [
|
||||
"safemem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
@ -448,6 +457,29 @@ version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pdf-writer"
|
||||
version = "0.4.1"
|
||||
@ -651,6 +683,12 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.136"
|
||||
@ -819,6 +857,7 @@ dependencies = [
|
||||
"memmap2",
|
||||
"miniz_oxide 0.4.4",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pdf-writer",
|
||||
"pico-args",
|
||||
"pixglyph",
|
||||
@ -966,6 +1005,49 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
|
||||
|
||||
[[package]]
|
||||
name = "xi-unicode"
|
||||
version = "0.3.0"
|
||||
|
@ -20,6 +20,7 @@ fxhash = "0.2"
|
||||
once_cell = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
typed-arena = "2"
|
||||
parking_lot = "0.12"
|
||||
|
||||
# Text and font handling
|
||||
hypher = "0.1"
|
||||
|
@ -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")
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
150
src/eval/mod.rs
150
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())
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
37
src/eval/str.rs
Normal file
37
src/eval/str.rs
Normal file
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
58
src/library/utility/color.rs
Normal file
58
src/library/utility/color.rs
Normal file
@ -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()))
|
||||
}
|
@ -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")?;
|
||||
|
@ -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)?))
|
||||
}
|
||||
|
@ -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> {
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -862,25 +888,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")
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 => {}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
BIN
tests/ref/utility/collection.png
Normal file
BIN
tests/ref/utility/collection.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
tests/ref/utility/string.png
Normal file
BIN
tests/ref/utility/string.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -32,10 +32,10 @@
|
||||
// Should output `2345`.
|
||||
#for v in (1, 2, 3, 4, 5, 6, 7) [#if v >= 2 and v <= 5 { repr(v) }]
|
||||
|
||||
// Loop over captured arguments.
|
||||
#let f1(..args) = for v in args { (repr(v),) }
|
||||
#let f2(..args) = for k, v in args { (repr(k) + ": " + repr(v),) }
|
||||
#let f(..args) = join(sep: ", ", ..f1(..args), ..f2(..args))
|
||||
// Map captured arguments.
|
||||
#let f1(..args) = args.positional().map(repr)
|
||||
#let f2(..args) = args.named().pairs((k, v) => repr(k) + ": " + repr(v))
|
||||
#let f(..args) = (f1(..args) + f2(..args)).join(", ")
|
||||
#f(1, a: 2)
|
||||
|
||||
---
|
||||
|
@ -60,10 +60,10 @@
|
||||
|
||||
#let nth(n) = {
|
||||
str(n)
|
||||
(if n == 1 { "st" }
|
||||
else if n == 2 { "nd" }
|
||||
else if n == 3 { "rd" }
|
||||
else { "th" })
|
||||
if n == 1 { "st" }
|
||||
else if n == 2 { "nd" }
|
||||
else if n == 3 { "rd" }
|
||||
else { "th" }
|
||||
}
|
||||
|
||||
#test(nth(1), "1st")
|
||||
|
50
tests/typ/code/methods.typ
Normal file
50
tests/typ/code/methods.typ
Normal file
@ -0,0 +1,50 @@
|
||||
// Test method calls.
|
||||
// Ref: false
|
||||
|
||||
---
|
||||
// Test whitespace around dot.
|
||||
#test( "Hi there" . split() , ("Hi", "there"))
|
||||
|
||||
---
|
||||
// Test mutating indexed value.
|
||||
{
|
||||
let matrix = (((1,), (2,)), ((3,), (4,)))
|
||||
matrix(1)(0).push(5)
|
||||
test(matrix, (((1,), (2,)), ((3, 5), (4,))))
|
||||
}
|
||||
|
||||
---
|
||||
// Test multiline chain in code block.
|
||||
{
|
||||
let rewritten = "Hello. This is a sentence. And one more."
|
||||
.split(".")
|
||||
.map(s => s.trim())
|
||||
.filter(s => s != "")
|
||||
.map(s => s + "!")
|
||||
.join([\ ])
|
||||
|
||||
test(rewritten, [Hello!\ This is a sentence!\ And one more!])
|
||||
}
|
||||
|
||||
---
|
||||
// Error: 2:3-2:16 type array has no method `fun`
|
||||
#let numbers = ()
|
||||
{ numbers.fun() }
|
||||
|
||||
---
|
||||
// Error: 2:3-2:44 cannot mutate a temporary value
|
||||
#let numbers = (1, 2, 3)
|
||||
{ numbers.map(v => v / 2).sorted().map(str).remove(4) }
|
||||
|
||||
---
|
||||
// Error: 2:3-2:19 cannot mutate a temporary value
|
||||
#let numbers = (1, 2, 3)
|
||||
{ numbers.sorted() = 1 }
|
||||
|
||||
---
|
||||
// Error: 3-6 cannot mutate a constant
|
||||
{ box = 1 }
|
||||
|
||||
---
|
||||
// Error: 3-6 cannot mutate a constant
|
||||
{ box.push(1) }
|
@ -65,19 +65,11 @@
|
||||
{ let x = 1; x += "2" }
|
||||
|
||||
---
|
||||
// Error: 13-14 expected argument list, found integer
|
||||
{ test with 2 }
|
||||
|
||||
---
|
||||
// Error: 3-4 expected function, found integer
|
||||
{ 1 with () }
|
||||
|
||||
---
|
||||
// Error: 3-6 cannot access this expression mutably
|
||||
// Error: 3-6 cannot mutate a temporary value
|
||||
{ (x) = "" }
|
||||
|
||||
---
|
||||
// Error: 3-8 cannot access this expression mutably
|
||||
// Error: 3-8 cannot mutate a temporary value
|
||||
{ 1 + 2 += 3 }
|
||||
|
||||
---
|
||||
|
@ -13,7 +13,7 @@
|
||||
#test(not "b" == "b", false)
|
||||
|
||||
// Assignment binds stronger than boolean operations.
|
||||
// Error: 2-7 cannot access this expression mutably
|
||||
// Error: 2-7 cannot mutate a temporary value
|
||||
{not x = "a"}
|
||||
|
||||
---
|
||||
|
@ -184,21 +184,19 @@
|
||||
{"a" not}
|
||||
|
||||
---
|
||||
// Test `with` operator.
|
||||
// Test `with` method.
|
||||
|
||||
// Apply positional arguments.
|
||||
#let add(x, y) = x + y
|
||||
#test((add with (2))(4), 6)
|
||||
|
||||
// Let .. with .. syntax.
|
||||
#let f = add
|
||||
#let f with (2)
|
||||
#test(f(4), 6)
|
||||
#test(add.with(2)(3), 5)
|
||||
#test(add.with(2).with(3)(), 5)
|
||||
#test((add.with(2))(4), 6)
|
||||
#test((add.with(2).with(3))(), 5)
|
||||
|
||||
// Make sure that named arguments are overridable.
|
||||
#let inc(x, y: 1) = x + y
|
||||
#test(inc(1), 2)
|
||||
|
||||
#let inc with (y: 2)
|
||||
#test(inc(2), 4)
|
||||
#test(inc(2, y: 4), 6)
|
||||
#let inc2 = inc.with(y: 2)
|
||||
#test(inc2(2), 4)
|
||||
#test(inc2(2, y: 4), 6)
|
||||
|
@ -7,6 +7,6 @@
|
||||
#let d = 3
|
||||
#let value = [hi]
|
||||
#let item(a, b) = a + b
|
||||
#let fn = rect with (fill: conifer, padding: 5pt)
|
||||
#let fn = rect.with(fill: conifer, padding: 5pt)
|
||||
|
||||
Some _includable_ text.
|
||||
|
@ -34,10 +34,10 @@
|
||||
#line(length: +30%, origin: (25.4%, 48%), angle: -36deg)
|
||||
#line(length: +30%, origin: (25.6%, 48%), angle: -72deg)
|
||||
#line(length: +32%, origin: (8.50%, 02%), angle: 34deg)
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
#align(center, grid(columns: (1fr, ) * 3, ..((star(20pt, thickness: .5pt), ) * 9)))
|
||||
#align(center, grid(columns: (1fr,) * 3, ..((star(20pt, thickness: .5pt),) * 9)))
|
||||
|
||||
---
|
||||
// Test errors.
|
||||
|
@ -1,22 +1,22 @@
|
||||
// Test shape fill & stroke.
|
||||
|
||||
---
|
||||
#let rect with (width: 20pt, height: 10pt)
|
||||
#let items = for i, rect in (
|
||||
rect(stroke: none),
|
||||
rect(),
|
||||
rect(fill: none),
|
||||
rect(thickness: 2pt),
|
||||
rect(stroke: eastern),
|
||||
rect(stroke: eastern, thickness: 2pt),
|
||||
rect(fill: eastern),
|
||||
rect(fill: eastern, stroke: none),
|
||||
rect(fill: forest, stroke: none, thickness: 2pt),
|
||||
rect(fill: forest, stroke: conifer),
|
||||
rect(fill: forest, stroke: black, thickness: 2pt),
|
||||
rect(fill: forest, stroke: conifer, thickness: 2pt),
|
||||
#let variant = rect.with(width: 20pt, height: 10pt)
|
||||
#let items = for i, item in (
|
||||
variant(stroke: none),
|
||||
variant(),
|
||||
variant(fill: none),
|
||||
variant(thickness: 2pt),
|
||||
variant(stroke: eastern),
|
||||
variant(stroke: eastern, thickness: 2pt),
|
||||
variant(fill: eastern),
|
||||
variant(fill: eastern, stroke: none),
|
||||
variant(fill: forest, stroke: none, thickness: 2pt),
|
||||
variant(fill: forest, stroke: conifer),
|
||||
variant(fill: forest, stroke: black, thickness: 2pt),
|
||||
variant(fill: forest, stroke: conifer, thickness: 2pt),
|
||||
) {
|
||||
(align(horizon)[{i + 1}.], rect, [])
|
||||
(align(horizon)[{i + 1}.], item, [])
|
||||
}
|
||||
|
||||
#grid(
|
||||
|
@ -19,8 +19,8 @@
|
||||
#overline(underline[Running amongst the wolves.])
|
||||
|
||||
---
|
||||
#let redact = strike with (10pt, extent: 5%)
|
||||
#let highlight = strike with (
|
||||
#let redact = strike.with(10pt, extent: 5%)
|
||||
#let highlight = strike.with(
|
||||
stroke: rgb("abcdef88"),
|
||||
thickness: 10pt,
|
||||
extent: 5%,
|
||||
|
@ -21,64 +21,4 @@
|
||||
// Test the `type` function.
|
||||
#test(type(1), "integer")
|
||||
#test(type(ltr), "direction")
|
||||
|
||||
---
|
||||
// Test the `repr` function.
|
||||
#test(repr(ltr), "ltr")
|
||||
#test(repr((1, 2, false, )), "(1, 2, false)")
|
||||
|
||||
---
|
||||
// Test the `join` function.
|
||||
#test(join(), none)
|
||||
#test(join(sep: false), none)
|
||||
#test(join(1), 1)
|
||||
#test(join("a", "b", "c"), "abc")
|
||||
#test("(" + join("a", "b", "c", sep: ", ") + ")", "(a, b, c)")
|
||||
|
||||
---
|
||||
// Test content joining.
|
||||
// Ref: true
|
||||
#join([One], [Two], [Three], sep: [, ]).
|
||||
|
||||
---
|
||||
// Error: 11-24 cannot join boolean with boolean
|
||||
#test(join(true, false))
|
||||
|
||||
---
|
||||
// Error: 11-29 cannot join string with integer
|
||||
#test(join("a", "b", sep: 1))
|
||||
|
||||
---
|
||||
// Test conversion functions.
|
||||
#test(int(false), 0)
|
||||
#test(int(true), 1)
|
||||
#test(int(10), 10)
|
||||
#test(int("150"), 150)
|
||||
#test(type(10 / 3), "float")
|
||||
#test(int(10 / 3), 3)
|
||||
#test(float(10), 10.0)
|
||||
#test(float("31.4e-1"), 3.14)
|
||||
#test(type(float(10)), "float")
|
||||
#test(str(123), "123")
|
||||
#test(str(50.14), "50.14")
|
||||
#test(len(str(10 / 3)) > 10, true)
|
||||
|
||||
---
|
||||
// Error: 6-10 cannot convert length to integer
|
||||
#int(10pt)
|
||||
|
||||
---
|
||||
// Error: 8-13 cannot convert function to float
|
||||
#float(float)
|
||||
|
||||
---
|
||||
// Error: 6-8 cannot convert content to string
|
||||
#str([])
|
||||
|
||||
---
|
||||
// Error: 6-12 invalid integer
|
||||
#int("nope")
|
||||
|
||||
---
|
||||
// Error: 8-15 invalid float
|
||||
#float("1.2.3")
|
||||
|
@ -2,42 +2,91 @@
|
||||
// Ref: false
|
||||
|
||||
---
|
||||
// Test the `len` function.
|
||||
#test(len(()), 0)
|
||||
#test(len(("A", "B", "C")), 3)
|
||||
#test(len("Hello World!"), 12)
|
||||
#test(len((a: 1, b: 2)), 2)
|
||||
// Test the `len` method.
|
||||
#test(().len(), 0)
|
||||
#test(("A", "B", "C").len(), 3)
|
||||
#test("Hello World!".len(), 12)
|
||||
#test((a: 1, b: 2).len(), 2)
|
||||
|
||||
---
|
||||
// Error: 5-7 missing argument: collection
|
||||
#len()
|
||||
// Test the `push` and `pop` methods.
|
||||
{
|
||||
let tasks = (a: (1, 2, 3), b: (4, 5, 6))
|
||||
tasks("a").pop()
|
||||
tasks("b").push(7)
|
||||
test(tasks("a"), (1, 2))
|
||||
test(tasks("b"), (4, 5, 6, 7))
|
||||
}
|
||||
|
||||
---
|
||||
// Error: 6-10 expected string, array or dictionary, found length
|
||||
#len(12pt)
|
||||
// Test the `insert` and `remove` methods.
|
||||
{
|
||||
let array = (0, 1, 2, 4, 5)
|
||||
array.insert(3, 3)
|
||||
test(array, range(6))
|
||||
array.remove(1)
|
||||
test(array, (0, 2, 3, 4, 5))
|
||||
}
|
||||
|
||||
---
|
||||
// Test the `upper` and `lower` functions.
|
||||
#let memes = "ArE mEmEs gReAt?";
|
||||
#test(lower(memes), "are memes great?")
|
||||
#test(upper(memes), "ARE MEMES GREAT?")
|
||||
#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
|
||||
// Test the `find` method.
|
||||
#test(("Hi", "❤️", "Love").find("❤️"), 1)
|
||||
#test(("Bye", "💘", "Apart").find("❤️"), none)
|
||||
|
||||
---
|
||||
// Error: 8-9 expected string or content, found integer
|
||||
#upper(1)
|
||||
// Test the `slice` method.
|
||||
#test((1, 2, 3, 4).slice(2), (3, 4))
|
||||
#test(range(10).slice(2, 6), (2, 3, 4, 5))
|
||||
#test(range(10).slice(4, count: 3), (4, 5, 6))
|
||||
|
||||
---
|
||||
// Test the `sorted` function.
|
||||
#test(sorted(()), ())
|
||||
#test(sorted((true, false) * 10), (false,) * 10 + (true,) * 10)
|
||||
#test(sorted(("it", "the", "hi", "text")), ("hi", "it", "text", "the"))
|
||||
#test(sorted((2, 1, 3, 10, 5, 8, 6, -7, 2)), (-7, 1, 2, 2, 3, 5, 6, 8, 10))
|
||||
// Error: 3-31 array index out of bounds (index: 12, len: 10)
|
||||
{ range(10).slice(9, count: 3) }
|
||||
|
||||
---
|
||||
// Error: 9-21 cannot order string and integer
|
||||
#sorted((1, 2, "ab"))
|
||||
// Error: 2:17-2:19 missing argument: index
|
||||
#let numbers = ()
|
||||
{ numbers.insert() }
|
||||
|
||||
---
|
||||
// Error: 9-24 cannot order content and content
|
||||
#sorted(([Hi], [There]))
|
||||
// Test the `join` method.
|
||||
#test(().join(), none)
|
||||
#test((1,).join(), 1)
|
||||
#test(("a", "b", "c").join(), "abc")
|
||||
#test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)")
|
||||
|
||||
---
|
||||
// Error: 2-22 cannot join boolean with boolean
|
||||
{(true, false).join()}
|
||||
|
||||
---
|
||||
// Error: 2-20 cannot join string with integer
|
||||
{("a", "b").join(1)}
|
||||
|
||||
---
|
||||
// Test joining content.
|
||||
// Ref: true
|
||||
{([One], [Two], [Three]).join([, ], last: [ and ])}.
|
||||
|
||||
---
|
||||
// Test the `sorted` method.
|
||||
#test(().sorted(), ())
|
||||
#test(((true, false) * 10).sorted(), (false,) * 10 + (true,) * 10)
|
||||
#test(("it", "the", "hi", "text").sorted(), ("hi", "it", "text", "the"))
|
||||
#test((2, 1, 3, 10, 5, 8, 6, -7, 2).sorted(), (-7, 1, 2, 2, 3, 5, 6, 8, 10))
|
||||
|
||||
---
|
||||
// Error: 2-26 cannot order content and content
|
||||
{([Hi], [There]).sorted()}
|
||||
|
||||
---
|
||||
// Test dictionary methods.
|
||||
#let dict = (a: 3, c: 2, b: 1)
|
||||
#test("c" in dict, true)
|
||||
#test(dict.len(), 3)
|
||||
#test(dict.values(), (3, 1, 2))
|
||||
#test(dict.pairs((k, v) => k + str(v)).join(), "a3b1c2")
|
||||
|
||||
{ dict.remove("c") }
|
||||
#test("c" in dict, false)
|
||||
#test(dict, (a: 3, b: 1))
|
||||
|
@ -1,6 +1,33 @@
|
||||
// Test math functions.
|
||||
// Ref: false
|
||||
|
||||
---
|
||||
// Test conversion to numbers.
|
||||
#test(int(false), 0)
|
||||
#test(int(true), 1)
|
||||
#test(int(10), 10)
|
||||
#test(int("150"), 150)
|
||||
#test(int(10 / 3), 3)
|
||||
#test(float(10), 10.0)
|
||||
#test(float("31.4e-1"), 3.14)
|
||||
#test(type(float(10)), "float")
|
||||
|
||||
---
|
||||
// Error: 6-10 cannot convert length to integer
|
||||
#int(10pt)
|
||||
|
||||
---
|
||||
// Error: 8-13 cannot convert function to float
|
||||
#float(float)
|
||||
|
||||
---
|
||||
// Error: 6-12 invalid integer
|
||||
#int("nope")
|
||||
|
||||
---
|
||||
// Error: 8-15 invalid float
|
||||
#float("1.2.3")
|
||||
|
||||
---
|
||||
// Test the `abs` function.
|
||||
#test(abs(-3), 3)
|
||||
|
@ -1,19 +0,0 @@
|
||||
// Test numbering formatting functions.
|
||||
|
||||
---
|
||||
#upper("Abc 8")
|
||||
#upper[def]
|
||||
|
||||
#lower("SCREAMING MUST BE SILENCED in " + roman(1672) + " years")
|
||||
|
||||
#for i in range(9) {
|
||||
symbol(i)
|
||||
[ and ]
|
||||
roman(i)
|
||||
[ for #i]
|
||||
parbreak()
|
||||
}
|
||||
|
||||
---
|
||||
// Error: 9-11 must be at least zero
|
||||
#symbol(-1)
|
52
tests/typ/utility/string.typ
Normal file
52
tests/typ/utility/string.typ
Normal file
@ -0,0 +1,52 @@
|
||||
// Test string related methods.
|
||||
// Ref: false
|
||||
|
||||
---
|
||||
// Test conversion to string.
|
||||
#test(str(123), "123")
|
||||
#test(str(50.14), "50.14")
|
||||
#test(str(10 / 3).len() > 10, true)
|
||||
#test(repr(ltr), "ltr")
|
||||
#test(repr((1, 2, false, )), "(1, 2, false)")
|
||||
|
||||
---
|
||||
// Error: 6-8 cannot convert content to string
|
||||
#str([])
|
||||
|
||||
---
|
||||
// Test the `split` and `trim` methods.
|
||||
#test(
|
||||
"Typst, LaTeX, Word, InDesign".split(",").map(s => s.trim()),
|
||||
("Typst", "LaTeX", "Word", "InDesign"),
|
||||
)
|
||||
|
||||
---
|
||||
// Test the `upper` and `lower` functions.
|
||||
#let memes = "ArE mEmEs gReAt?";
|
||||
#test(lower(memes), "are memes great?")
|
||||
#test(upper(memes), "ARE MEMES GREAT?")
|
||||
#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
|
||||
|
||||
---
|
||||
// Error: 8-9 expected string or content, found integer
|
||||
#upper(1)
|
||||
|
||||
---
|
||||
// Error: 9-11 must be at least zero
|
||||
#symbol(-1)
|
||||
|
||||
---
|
||||
// Test integrated lower, upper and symbols.
|
||||
// Ref: true
|
||||
#upper("Abc 8")
|
||||
#upper[def]
|
||||
|
||||
#lower("SCREAMING MUST BE SILENCED in " + roman(1672) + " years")
|
||||
|
||||
#for i in range(9) {
|
||||
symbol(i)
|
||||
[ and ]
|
||||
roman(i)
|
||||
[ for #i]
|
||||
parbreak()
|
||||
}
|
@ -124,7 +124,7 @@
|
||||
},
|
||||
{
|
||||
"name": "keyword.other.typst",
|
||||
"match": "(#)(as|in|with|from)\\b",
|
||||
"match": "(#)(as|in|from)\\b",
|
||||
"captures": { "1": { "name": "punctuation.definition.keyword.typst" } }
|
||||
},
|
||||
{
|
||||
@ -168,19 +168,19 @@
|
||||
{
|
||||
"comment": "Function name",
|
||||
"name": "entity.name.function.typst",
|
||||
"match": "((#)[[:alpha:]_][[:alnum:]._-]*!?)(?=\\[|\\()",
|
||||
"match": "((#)[[:alpha:]_][[:alnum:]_-]*!?)(?=\\[|\\()",
|
||||
"captures": { "2": { "name": "punctuation.definition.function.typst" } }
|
||||
},
|
||||
{
|
||||
"comment": "Function arguments",
|
||||
"begin": "(?<=#[[:alpha:]_][[:alnum:]._-]*!?)\\(",
|
||||
"begin": "(?<=#[[:alpha:]_][[:alnum:]_-]*!?)\\(",
|
||||
"end": "\\)",
|
||||
"captures": { "0": { "name": "punctuation.definition.group.typst" } },
|
||||
"patterns": [{ "include": "#arguments" }]
|
||||
},
|
||||
{
|
||||
"name": "variable.interpolated.typst",
|
||||
"match": "(#)[[:alpha:]_][[:alnum:]._-]*",
|
||||
"match": "(#)[[:alpha:]_][[:alnum:]_-]*",
|
||||
"captures": { "1": { "name": "punctuation.definition.variable.typst" } }
|
||||
}
|
||||
]
|
||||
@ -216,7 +216,7 @@
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.arithmetic.typst",
|
||||
"match": "\\+|\\*|/|(?<![[:alpha:]_][[:alnum:]._-]*)-(?![:alnum:]_-]*[[:alpha:]_])"
|
||||
"match": "\\+|\\*|/|(?<![[:alpha:]_][[:alnum:]_-]*)-(?![:alnum:]_-]*[[:alpha:]_])"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.word.typst",
|
||||
@ -224,7 +224,7 @@
|
||||
},
|
||||
{
|
||||
"name": "keyword.other.typst",
|
||||
"match": "\\b(pub|let|set|show|wrap|as|in|with|from)\\b"
|
||||
"match": "\\b(pub|let|set|show|wrap|as|in|from)\\b"
|
||||
},
|
||||
{
|
||||
"name": "keyword.control.conditional.typst",
|
||||
@ -246,18 +246,18 @@
|
||||
{
|
||||
"comment": "Function name",
|
||||
"name": "entity.name.function.typst",
|
||||
"match": "\\b[[:alpha:]_][[:alnum:]._-]*!?(?=(\\[|\\()|\\s+\\bwith\\b)"
|
||||
"match": "\\b[[:alpha:]_][[:alnum:]_-]*!?(?=\\[|\\()"
|
||||
},
|
||||
{
|
||||
"comment": "Function arguments",
|
||||
"begin": "(?<=\\b[[:alpha:]_][[:alnum:]._-]*!?|\\bwith\\b\\s+)\\(",
|
||||
"begin": "(?<=\\b[[:alpha:]_][[:alnum:]_-]*!?)\\(",
|
||||
"end": "\\)",
|
||||
"captures": { "0": { "name": "punctuation.definition.group.typst" } },
|
||||
"patterns": [{ "include": "#arguments" }]
|
||||
},
|
||||
{
|
||||
"name": "variable.other.typst",
|
||||
"match": "\\b[[:alpha:]_][[:alnum:]._-]*\\b"
|
||||
"match": "\\b[[:alpha:]_][[:alnum:]_-]*\\b"
|
||||
},
|
||||
{
|
||||
"name": "meta.group.typst",
|
||||
@ -306,10 +306,6 @@
|
||||
"name": "constant.numeric.float.typst",
|
||||
"match": "\\b(\\d*)?\\.?\\d+([eE][+-]?\\d+)?\\b"
|
||||
},
|
||||
{
|
||||
"name": "constant.other.color.typst",
|
||||
"match": "#[0-9a-zA-Z]+\\b"
|
||||
},
|
||||
{
|
||||
"name": "string.quoted.double.typst",
|
||||
"begin": "\"",
|
||||
@ -326,7 +322,7 @@
|
||||
"patterns": [
|
||||
{
|
||||
"name": "variable.parameter.typst",
|
||||
"match": "\\b[[:alpha:]_][[:alnum:]._-]*(?=:)"
|
||||
"match": "\\b[[:alpha:]_][[:alnum:]_-]*(?=:)"
|
||||
},
|
||||
{ "include": "#code" }
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user