mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Remove index syntax in favor of accessor methods
This commit is contained in:
parent
fe1f440069
commit
f70cea508c
@ -165,36 +165,6 @@ impl Args {
|
|||||||
.filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone())))
|
.filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone())))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reinterpret these arguments as actually being an array index.
|
|
||||||
pub fn into_index(self) -> SourceResult<i64> {
|
|
||||||
self.into_castable("index")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reinterpret these arguments as actually being a dictionary key.
|
|
||||||
pub fn into_key(self) -> SourceResult<Str> {
|
|
||||||
self.into_castable("key")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reinterpret these arguments as actually being a single castable thing.
|
|
||||||
fn into_castable<T: Cast>(self, what: &str) -> SourceResult<T> {
|
|
||||||
let mut iter = self.items.into_iter();
|
|
||||||
let value = match iter.next() {
|
|
||||||
Some(Arg { name: None, value, .. }) => value.v.cast().at(value.span)?,
|
|
||||||
None => {
|
|
||||||
bail!(self.span, "missing {}", what);
|
|
||||||
}
|
|
||||||
Some(Arg { name: Some(_), span, .. }) => {
|
|
||||||
bail!(span, "named pair is not allowed here");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(arg) = iter.next() {
|
|
||||||
bail!(arg.span, "only one {} is allowed", what);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Args {
|
impl Debug for Args {
|
||||||
|
@ -4,7 +4,7 @@ use std::ops::{Add, AddAssign};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{ops, Args, Func, Value, Vm};
|
use super::{ops, Args, Func, Value, Vm};
|
||||||
use crate::diag::{At, SourceResult, StrResult};
|
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::util::{format_eco, ArcExt, EcoString};
|
use crate::util::{format_eco, ArcExt, EcoString};
|
||||||
|
|
||||||
@ -45,24 +45,34 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The first value in the array.
|
/// The first value in the array.
|
||||||
pub fn first(&self) -> Option<&Value> {
|
pub fn first(&self) -> StrResult<&Value> {
|
||||||
self.0.first()
|
self.0.first().ok_or_else(array_is_empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutably borrow the first value in the array.
|
||||||
|
pub fn first_mut(&mut self) -> StrResult<&mut Value> {
|
||||||
|
Arc::make_mut(&mut self.0).first_mut().ok_or_else(array_is_empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The last value in the array.
|
/// The last value in the array.
|
||||||
pub fn last(&self) -> Option<&Value> {
|
pub fn last(&self) -> StrResult<&Value> {
|
||||||
self.0.last()
|
self.0.last().ok_or_else(array_is_empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutably borrow the last value in the array.
|
||||||
|
pub fn last_mut(&mut self) -> StrResult<&mut Value> {
|
||||||
|
Arc::make_mut(&mut self.0).last_mut().ok_or_else(array_is_empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow the value at the given index.
|
/// Borrow the value at the given index.
|
||||||
pub fn get(&self, index: i64) -> StrResult<&Value> {
|
pub fn at(&self, index: i64) -> StrResult<&Value> {
|
||||||
self.locate(index)
|
self.locate(index)
|
||||||
.and_then(|i| self.0.get(i))
|
.and_then(|i| self.0.get(i))
|
||||||
.ok_or_else(|| out_of_bounds(index, self.len()))
|
.ok_or_else(|| out_of_bounds(index, self.len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutably borrow the value at the given index.
|
/// Mutably borrow the value at the given index.
|
||||||
pub fn get_mut(&mut self, index: i64) -> StrResult<&mut Value> {
|
pub fn at_mut(&mut self, index: i64) -> StrResult<&mut Value> {
|
||||||
let len = self.len();
|
let len = self.len();
|
||||||
self.locate(index)
|
self.locate(index)
|
||||||
.and_then(move |i| Arc::make_mut(&mut self.0).get_mut(i))
|
.and_then(move |i| Arc::make_mut(&mut self.0).get_mut(i))
|
||||||
@ -128,6 +138,9 @@ impl Array {
|
|||||||
|
|
||||||
/// Return the first matching element.
|
/// Return the first matching element.
|
||||||
pub fn find(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Option<Value>> {
|
pub fn find(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Option<Value>> {
|
||||||
|
if f.v.argc().map_or(false, |count| count != 1) {
|
||||||
|
bail!(f.span, "function must have exactly one parameter");
|
||||||
|
}
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
@ -140,6 +153,9 @@ impl Array {
|
|||||||
|
|
||||||
/// Return the index of the first matching element.
|
/// Return the index of the first matching element.
|
||||||
pub fn position(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Option<i64>> {
|
pub fn position(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Option<i64>> {
|
||||||
|
if f.v.argc().map_or(false, |count| count != 1) {
|
||||||
|
bail!(f.span, "function must have exactly one parameter");
|
||||||
|
}
|
||||||
for (i, item) in self.iter().enumerate() {
|
for (i, item) in self.iter().enumerate() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
@ -153,6 +169,9 @@ impl Array {
|
|||||||
/// Return a new array with only those elements for which the function
|
/// Return a new array with only those elements for which the function
|
||||||
/// returns true.
|
/// returns true.
|
||||||
pub fn filter(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Self> {
|
pub fn filter(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Self> {
|
||||||
|
if f.v.argc().map_or(false, |count| count != 1) {
|
||||||
|
bail!(f.span, "function must have exactly one parameter");
|
||||||
|
}
|
||||||
let mut kept = vec![];
|
let mut kept = vec![];
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
@ -165,6 +184,9 @@ impl Array {
|
|||||||
|
|
||||||
/// Transform each item in the array with a function.
|
/// Transform each item in the array with a function.
|
||||||
pub fn map(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Self> {
|
pub fn map(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Self> {
|
||||||
|
if f.v.argc().map_or(false, |count| count < 1 || count > 2) {
|
||||||
|
bail!(f.span, "function must have one or two parameters");
|
||||||
|
}
|
||||||
let enumerate = f.v.argc() == Some(2);
|
let enumerate = f.v.argc() == Some(2);
|
||||||
self.iter()
|
self.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@ -179,8 +201,24 @@ impl Array {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fold all of the array's elements into one with a function.
|
||||||
|
pub fn fold(&self, vm: &Vm, init: Value, f: Spanned<Func>) -> SourceResult<Value> {
|
||||||
|
if f.v.argc().map_or(false, |count| count != 2) {
|
||||||
|
bail!(f.span, "function must have exactly two parameters");
|
||||||
|
}
|
||||||
|
let mut acc = init;
|
||||||
|
for item in self.iter() {
|
||||||
|
let args = Args::new(f.span, [acc, item.clone()]);
|
||||||
|
acc = f.v.call(vm, args)?;
|
||||||
|
}
|
||||||
|
Ok(acc)
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether any element matches.
|
/// Whether any element matches.
|
||||||
pub fn any(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<bool> {
|
pub fn any(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<bool> {
|
||||||
|
if f.v.argc().map_or(false, |count| count != 1) {
|
||||||
|
bail!(f.span, "function must have exactly one parameter");
|
||||||
|
}
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
@ -193,6 +231,9 @@ impl Array {
|
|||||||
|
|
||||||
/// Whether all elements match.
|
/// Whether all elements match.
|
||||||
pub fn all(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<bool> {
|
pub fn all(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<bool> {
|
||||||
|
if f.v.argc().map_or(false, |count| count != 1) {
|
||||||
|
bail!(f.span, "function must have exactly one parameter");
|
||||||
|
}
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
if !f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
if !f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
|
@ -4,7 +4,7 @@ use std::ops::{Add, AddAssign};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{Args, Array, Func, Str, Value, Vm};
|
use super::{Args, Array, Func, Str, Value, Vm};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{bail, SourceResult, StrResult};
|
||||||
use crate::syntax::is_ident;
|
use crate::syntax::is_ident;
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::util::{format_eco, ArcExt, EcoString};
|
use crate::util::{format_eco, ArcExt, EcoString};
|
||||||
@ -50,7 +50,7 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow the value the given `key` maps to.
|
/// Borrow the value the given `key` maps to.
|
||||||
pub fn get(&self, key: &str) -> StrResult<&Value> {
|
pub fn at(&self, key: &str) -> StrResult<&Value> {
|
||||||
self.0.get(key).ok_or_else(|| missing_key(key))
|
self.0.get(key).ok_or_else(|| missing_key(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ impl Dict {
|
|||||||
///
|
///
|
||||||
/// This inserts the key with [`None`](Value::None) as the value if not
|
/// This inserts the key with [`None`](Value::None) as the value if not
|
||||||
/// present so far.
|
/// present so far.
|
||||||
pub fn get_mut(&mut self, key: Str) -> &mut Value {
|
pub fn at_mut(&mut self, key: Str) -> &mut Value {
|
||||||
Arc::make_mut(&mut self.0).entry(key).or_default()
|
Arc::make_mut(&mut self.0).entry(key).or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +108,9 @@ impl Dict {
|
|||||||
|
|
||||||
/// Transform each pair in the dictionary with a function.
|
/// Transform each pair in the dictionary with a function.
|
||||||
pub fn map(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Array> {
|
pub fn map(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Array> {
|
||||||
|
if f.v.argc().map_or(false, |count| count != 1) {
|
||||||
|
bail!(f.span, "function must have exactly two parameters");
|
||||||
|
}
|
||||||
self.iter()
|
self.iter()
|
||||||
.map(|(key, value)| {
|
.map(|(key, value)| {
|
||||||
let args = Args::new(f.span, [Value::Str(key.clone()), value.clone()]);
|
let args = Args::new(f.span, [Value::Str(key.clone()), value.clone()]);
|
||||||
|
@ -780,7 +780,7 @@ impl Eval for ast::FieldAccess {
|
|||||||
let field = self.field().take();
|
let field = self.field().take();
|
||||||
|
|
||||||
Ok(match object {
|
Ok(match object {
|
||||||
Value::Dict(dict) => dict.get(&field).at(span)?.clone(),
|
Value::Dict(dict) => dict.at(&field).at(span)?.clone(),
|
||||||
Value::Content(content) => content
|
Value::Content(content) => content
|
||||||
.field(&field)
|
.field(&field)
|
||||||
.ok_or_else(|| format!("unknown field {field:?}"))
|
.ok_or_else(|| format!("unknown field {field:?}"))
|
||||||
@ -798,22 +798,11 @@ impl Eval for ast::FuncCall {
|
|||||||
bail!(self.span(), "maximum function call depth exceeded");
|
bail!(self.span(), "maximum function call depth exceeded");
|
||||||
}
|
}
|
||||||
|
|
||||||
let callee = self.callee().eval(vm)?;
|
let callee = self.callee();
|
||||||
|
let callee = callee.eval(vm)?.cast::<Func>().at(callee.span())?;
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
|
let point = || Tracepoint::Call(callee.name().map(Into::into));
|
||||||
Ok(match callee {
|
callee.call(vm, args).trace(vm.world, point, self.span())
|
||||||
Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(),
|
|
||||||
Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(),
|
|
||||||
Value::Func(func) => {
|
|
||||||
let point = || Tracepoint::Call(func.name().map(Into::into));
|
|
||||||
func.call(vm, args).trace(vm.world, point, self.span())?
|
|
||||||
}
|
|
||||||
v => bail!(
|
|
||||||
self.callee().span(),
|
|
||||||
"expected callable or collection, found {}",
|
|
||||||
v.type_name(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1246,9 +1235,13 @@ impl Access for ast::Expr {
|
|||||||
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||||
match self {
|
match self {
|
||||||
Self::Ident(v) => v.access(vm),
|
Self::Ident(v) => v.access(vm),
|
||||||
|
Self::Parenthesized(v) => v.access(vm),
|
||||||
Self::FieldAccess(v) => v.access(vm),
|
Self::FieldAccess(v) => v.access(vm),
|
||||||
Self::FuncCall(v) => v.access(vm),
|
Self::MethodCall(v) => v.access(vm),
|
||||||
_ => bail!(self.span(), "cannot mutate a temporary value"),
|
_ => {
|
||||||
|
let _ = self.eval(vm)?;
|
||||||
|
bail!(self.span(), "cannot mutate a temporary value");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1259,10 +1252,16 @@ impl Access for ast::Ident {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Access for ast::Parenthesized {
|
||||||
|
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||||
|
self.expr().access(vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Access for ast::FieldAccess {
|
impl Access for ast::FieldAccess {
|
||||||
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||||
Ok(match self.target().access(vm)? {
|
Ok(match self.target().access(vm)? {
|
||||||
Value::Dict(dict) => dict.get_mut(self.field().take().into()),
|
Value::Dict(dict) => dict.at_mut(self.field().take().into()),
|
||||||
v => bail!(
|
v => bail!(
|
||||||
self.target().span(),
|
self.target().span(),
|
||||||
"expected dictionary, found {}",
|
"expected dictionary, found {}",
|
||||||
@ -1272,17 +1271,17 @@ impl Access for ast::FieldAccess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for ast::FuncCall {
|
impl Access for ast::MethodCall {
|
||||||
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||||
|
let span = self.span();
|
||||||
|
let method = self.method();
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
Ok(match self.callee().access(vm)? {
|
if methods::is_accessor(&method) {
|
||||||
Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?,
|
let value = self.target().access(vm)?;
|
||||||
Value::Dict(dict) => dict.get_mut(args.into_key()?),
|
methods::call_access(value, &method, args, span)
|
||||||
v => bail!(
|
} else {
|
||||||
self.callee().span(),
|
let _ = self.eval(vm)?;
|
||||||
"expected collection, found {}",
|
bail!(span, "cannot mutate a temporary value");
|
||||||
v.type_name(),
|
}
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Methods on values.
|
//! Methods on values.
|
||||||
|
|
||||||
use super::{Args, Value, Vm};
|
use super::{Args, Str, Value, Vm};
|
||||||
use crate::diag::{At, SourceResult};
|
use crate::diag::{At, SourceResult};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -26,6 +26,9 @@ pub fn call(
|
|||||||
|
|
||||||
Value::Str(string) => match method {
|
Value::Str(string) => match method {
|
||||||
"len" => Value::Int(string.len() as i64),
|
"len" => Value::Int(string.len() as i64),
|
||||||
|
"first" => Value::Str(string.first().at(span)?),
|
||||||
|
"last" => Value::Str(string.last().at(span)?),
|
||||||
|
"at" => Value::Str(string.at(args.expect("index")?).at(span)?),
|
||||||
"slice" => {
|
"slice" => {
|
||||||
let start = args.expect("start")?;
|
let start = args.expect("start")?;
|
||||||
let mut end = args.eat()?;
|
let mut end = args.eat()?;
|
||||||
@ -65,8 +68,9 @@ pub fn call(
|
|||||||
|
|
||||||
Value::Array(array) => match method {
|
Value::Array(array) => match method {
|
||||||
"len" => Value::Int(array.len()),
|
"len" => Value::Int(array.len()),
|
||||||
"first" => array.first().cloned().unwrap_or(Value::None),
|
"first" => array.first().at(span)?.clone(),
|
||||||
"last" => array.last().cloned().unwrap_or(Value::None),
|
"last" => array.last().at(span)?.clone(),
|
||||||
|
"at" => array.at(args.expect("index")?).at(span)?.clone(),
|
||||||
"slice" => {
|
"slice" => {
|
||||||
let start = args.expect("start")?;
|
let start = args.expect("start")?;
|
||||||
let mut end = args.eat()?;
|
let mut end = args.eat()?;
|
||||||
@ -82,6 +86,9 @@ pub fn call(
|
|||||||
.map_or(Value::None, Value::Int),
|
.map_or(Value::None, Value::Int),
|
||||||
"filter" => Value::Array(array.filter(vm, args.expect("function")?)?),
|
"filter" => Value::Array(array.filter(vm, args.expect("function")?)?),
|
||||||
"map" => Value::Array(array.map(vm, args.expect("function")?)?),
|
"map" => Value::Array(array.map(vm, args.expect("function")?)?),
|
||||||
|
"fold" => {
|
||||||
|
array.fold(vm, args.expect("initial value")?, args.expect("function")?)?
|
||||||
|
}
|
||||||
"any" => Value::Bool(array.any(vm, args.expect("function")?)?),
|
"any" => Value::Bool(array.any(vm, args.expect("function")?)?),
|
||||||
"all" => Value::Bool(array.all(vm, args.expect("function")?)?),
|
"all" => Value::Bool(array.all(vm, args.expect("function")?)?),
|
||||||
"flatten" => Value::Array(array.flatten()),
|
"flatten" => Value::Array(array.flatten()),
|
||||||
@ -97,6 +104,7 @@ pub fn call(
|
|||||||
|
|
||||||
Value::Dict(dict) => match method {
|
Value::Dict(dict) => match method {
|
||||||
"len" => Value::Int(dict.len()),
|
"len" => Value::Int(dict.len()),
|
||||||
|
"at" => dict.at(&args.expect::<Str>("key")?).cloned().at(span)?,
|
||||||
"keys" => Value::Array(dict.keys()),
|
"keys" => Value::Array(dict.keys()),
|
||||||
"values" => Value::Array(dict.values()),
|
"values" => Value::Array(dict.values()),
|
||||||
"pairs" => Value::Array(dict.map(vm, args.expect("function")?)?),
|
"pairs" => Value::Array(dict.map(vm, args.expect("function")?)?),
|
||||||
@ -158,11 +166,44 @@ pub fn call_mut(
|
|||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call an accessor method on a value.
|
||||||
|
pub fn call_access<'a>(
|
||||||
|
value: &'a mut Value,
|
||||||
|
method: &str,
|
||||||
|
mut args: Args,
|
||||||
|
span: Span,
|
||||||
|
) -> SourceResult<&'a mut Value> {
|
||||||
|
let name = value.type_name();
|
||||||
|
let missing = || Err(missing_method(name, method)).at(span);
|
||||||
|
|
||||||
|
let slot = match value {
|
||||||
|
Value::Array(array) => match method {
|
||||||
|
"first" => array.first_mut().at(span)?,
|
||||||
|
"last" => array.last_mut().at(span)?,
|
||||||
|
"at" => array.at_mut(args.expect("index")?).at(span)?,
|
||||||
|
_ => return missing(),
|
||||||
|
},
|
||||||
|
Value::Dict(dict) => match method {
|
||||||
|
"at" => dict.at_mut(args.expect("index")?),
|
||||||
|
_ => return missing(),
|
||||||
|
},
|
||||||
|
_ => return missing(),
|
||||||
|
};
|
||||||
|
|
||||||
|
args.finish()?;
|
||||||
|
Ok(slot)
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether a specific method is mutating.
|
/// Whether a specific method is mutating.
|
||||||
pub fn is_mutating(method: &str) -> bool {
|
pub fn is_mutating(method: &str) -> bool {
|
||||||
matches!(method, "push" | "pop" | "insert" | "remove")
|
matches!(method, "push" | "pop" | "insert" | "remove")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether a specific method is an accessor.
|
||||||
|
pub fn is_accessor(method: &str) -> bool {
|
||||||
|
matches!(method, "first" | "last" | "at")
|
||||||
|
}
|
||||||
|
|
||||||
/// The missing method error message.
|
/// The missing method error message.
|
||||||
#[cold]
|
#[cold]
|
||||||
fn missing_method(type_name: &str, method: &str) -> String {
|
fn missing_method(type_name: &str, method: &str) -> String {
|
||||||
|
@ -42,16 +42,40 @@ impl Str {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The codepoints the string consists of.
|
|
||||||
pub fn codepoints(&self) -> Array {
|
|
||||||
self.as_str().chars().map(|c| Value::Str(c.into())).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The grapheme clusters the string consists of.
|
/// The grapheme clusters the string consists of.
|
||||||
pub fn graphemes(&self) -> Array {
|
pub fn graphemes(&self) -> Array {
|
||||||
self.as_str().graphemes(true).map(|s| Value::Str(s.into())).collect()
|
self.as_str().graphemes(true).map(|s| Value::Str(s.into())).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract the first grapheme cluster.
|
||||||
|
pub fn first(&self) -> StrResult<Self> {
|
||||||
|
self.0
|
||||||
|
.graphemes(true)
|
||||||
|
.next()
|
||||||
|
.map(Into::into)
|
||||||
|
.ok_or_else(string_is_empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the last grapheme cluster.
|
||||||
|
pub fn last(&self) -> StrResult<Self> {
|
||||||
|
self.0
|
||||||
|
.graphemes(true)
|
||||||
|
.next_back()
|
||||||
|
.map(Into::into)
|
||||||
|
.ok_or_else(string_is_empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the grapheme cluster at the given index.
|
||||||
|
pub fn at(&self, index: i64) -> StrResult<Self> {
|
||||||
|
let len = self.len();
|
||||||
|
let grapheme = self
|
||||||
|
.locate(index)
|
||||||
|
.filter(|&index| index <= self.0.len())
|
||||||
|
.and_then(|index| self.0[index..].graphemes(true).next())
|
||||||
|
.ok_or_else(|| out_of_bounds(index, len))?;
|
||||||
|
Ok(grapheme.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract a contigous substring.
|
/// Extract a contigous substring.
|
||||||
pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> {
|
pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> {
|
||||||
let len = self.len();
|
let len = self.len();
|
||||||
@ -270,6 +294,12 @@ fn out_of_bounds(index: i64, len: i64) -> String {
|
|||||||
format!("string index out of bounds (index: {}, len: {})", index, len)
|
format!("string index out of bounds (index: {}, len: {})", index, len)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The error message when the string is empty.
|
||||||
|
#[cold]
|
||||||
|
fn string_is_empty() -> EcoString {
|
||||||
|
"string is empty".into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert an item of std's `match_indices` to a dictionary.
|
/// Convert an item of std's `match_indices` to a dictionary.
|
||||||
fn match_to_dict((start, text): (usize, &str)) -> Dict {
|
fn match_to_dict((start, text): (usize, &str)) -> Dict {
|
||||||
dict! {
|
dict! {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 506 B After Width: | Height: | Size: 506 B |
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
@ -39,11 +39,14 @@
|
|||||||
---
|
---
|
||||||
// Test numbering with closure.
|
// Test numbering with closure.
|
||||||
#enum(
|
#enum(
|
||||||
start: 4,
|
start: 4,
|
||||||
spacing: 0.65em - 3pt,
|
spacing: 0.65em - 3pt,
|
||||||
tight: false,
|
tight: false,
|
||||||
numbering: n => text(fill: (red, green, blue)(mod(n, 3)), numbering("A", n)),
|
numbering: n => text(
|
||||||
[Red], [Green], [Blue],
|
fill: (red, green, blue).at(mod(n, 3)),
|
||||||
|
numbering("A", n),
|
||||||
|
),
|
||||||
|
[Red], [Green], [Blue],
|
||||||
)
|
)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -40,7 +40,7 @@ No: list \
|
|||||||
#show terms: it => table(
|
#show terms: it => table(
|
||||||
columns: 2,
|
columns: 2,
|
||||||
inset: 3pt,
|
inset: 3pt,
|
||||||
..it.items.map(item => (emph(item(0)), item(1))).flatten(),
|
..it.items.map(item => (emph(item.at(0)), item.at(1))).flatten(),
|
||||||
)
|
)
|
||||||
|
|
||||||
/ A: One letter
|
/ A: One letter
|
||||||
|
@ -23,51 +23,187 @@
|
|||||||
, rgb("002")
|
, rgb("002")
|
||||||
,)}
|
,)}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `len` method.
|
||||||
|
#test(().len(), 0)
|
||||||
|
#test(("A", "B", "C").len(), 3)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test lvalue and rvalue access.
|
// Test lvalue and rvalue access.
|
||||||
{
|
{
|
||||||
let array = (1, 2)
|
let array = (1, 2)
|
||||||
array(1) += 5 + array(0)
|
array.at(1) += 5 + array.at(0)
|
||||||
test(array, (1, 8))
|
test(array, (1, 8))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test different lvalue method.
|
||||||
|
{
|
||||||
|
let array = (1, 2, 3)
|
||||||
|
array.first() = 7
|
||||||
|
array.at(1) *= 8
|
||||||
|
test(array, (7, 16, 3))
|
||||||
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test rvalue out of bounds.
|
// Test rvalue out of bounds.
|
||||||
// Error: 2-14 array index out of bounds (index: 5, len: 3)
|
// Error: 2-17 array index out of bounds (index: 5, len: 3)
|
||||||
{(1, 2, 3)(5)}
|
{(1, 2, 3).at(5)}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test lvalue out of bounds.
|
// Test lvalue out of bounds.
|
||||||
{
|
{
|
||||||
let array = (1, 2, 3)
|
let array = (1, 2, 3)
|
||||||
// Error: 3-11 array index out of bounds (index: 3, len: 3)
|
// Error: 3-14 array index out of bounds (index: 3, len: 3)
|
||||||
array(3) = 5
|
array.at(3) = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test bad lvalue.
|
||||||
|
// Error: 2:3-2:14 cannot mutate a temporary value
|
||||||
|
#let array = (1, 2, 3)
|
||||||
|
{ array.len() = 4 }
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test bad lvalue.
|
||||||
|
// Error: 2:3-2:15 type array has no method `yolo`
|
||||||
|
#let array = (1, 2, 3)
|
||||||
|
{ array.yolo() = 4 }
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test negative indices.
|
// Test negative indices.
|
||||||
{
|
{
|
||||||
let array = (1, 2, 3, 4)
|
let array = (1, 2, 3, 4)
|
||||||
test(array(0), 1)
|
test(array.at(0), 1)
|
||||||
test(array(-1), 4)
|
test(array.at(-1), 4)
|
||||||
test(array(-2), 3)
|
test(array.at(-2), 3)
|
||||||
test(array(-3), 2)
|
test(array.at(-3), 2)
|
||||||
test(array(-4), 1)
|
test(array.at(-4), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 2-15 array index out of bounds (index: -4, len: 3)
|
// The the `first` and `last` methods.
|
||||||
{(1, 2, 3)(-4)}
|
#test((1,).first(), 1)
|
||||||
|
#test((2,).last(), 2)
|
||||||
|
#test((1, 2, 3).first(), 1)
|
||||||
|
#test((1, 2, 3).last(), 3)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test non-collection indexing.
|
// Error: 3-13 array is empty
|
||||||
|
{ ().first() }
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 3-12 array is empty
|
||||||
|
{ ().last() }
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `push` and `pop` methods.
|
||||||
{
|
{
|
||||||
let x = 10pt
|
let tasks = (a: (1, 2, 3), b: (4, 5, 6))
|
||||||
// Error: 3-4 expected collection, found length
|
tasks.at("a").pop()
|
||||||
x() = 1
|
tasks.b.push(7)
|
||||||
|
test(tasks.a, (1, 2))
|
||||||
|
test(tasks.at("b"), (4, 5, 6, 7))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 2:17-2:19 missing argument: index
|
||||||
|
#let numbers = ()
|
||||||
|
{ numbers.insert() }
|
||||||
|
---
|
||||||
|
// 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(range(10).slice(-5, count: 2), (5, 6))
|
||||||
|
#test((1, 2, 3).slice(2, -2), ())
|
||||||
|
#test((1, 2, 3).slice(-2, 2), (2,))
|
||||||
|
#test((1, 2, 3).slice(-3, 2), (1, 2))
|
||||||
|
#test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 3-31 array index out of bounds (index: 12, len: 10)
|
||||||
|
{ range(10).slice(9, count: 3) }
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 3-25 array index out of bounds (index: -4, len: 3)
|
||||||
|
{ (1, 2, 3).slice(0, -4) }
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `position` method.
|
||||||
|
#test(("Hi", "❤️", "Love").position(s => s == "❤️"), 1)
|
||||||
|
#test(("Bye", "💘", "Apart").position(s => s == "❤️"), none)
|
||||||
|
#test(("A", "B", "CDEF", "G").position(v => v.len() > 2), 2)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `filter` method.
|
||||||
|
#test(().filter(even), ())
|
||||||
|
#test((1, 2, 3, 4).filter(even), (2, 4))
|
||||||
|
#test((7, 3, 2, 5, 1).filter(x => x < 5), (3, 2, 1))
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `map` method.
|
||||||
|
#test(().map(x => x * 2), ())
|
||||||
|
#test((2, 3).map(x => x * 2), (4, 6))
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `fold` method.
|
||||||
|
#test(().fold("hi", grid), "hi")
|
||||||
|
#test((1, 2, 3, 4).fold(0, (s, x) => s + x), 10)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 21-31 function must have exactly two parameters
|
||||||
|
{ (1, 2, 3).fold(0, () => none) }
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `rev` method.
|
||||||
|
#test(range(3).rev(), (2, 1, 0))
|
||||||
|
|
||||||
|
---
|
||||||
|
// 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()}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 2-18 array index out of bounds (index: -4, len: 3)
|
||||||
|
{(1, 2, 3).at(-4)}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3 expected closing paren
|
// Error: 3 expected closing paren
|
||||||
{(}
|
{(}
|
||||||
|
@ -48,25 +48,25 @@
|
|||||||
#set text(family: "Arial", family: "Helvetica")
|
#set text(family: "Arial", family: "Helvetica")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 2-6 expected callable or collection, found boolean
|
// Error: 2-6 expected function, found boolean
|
||||||
{true()}
|
{true()}
|
||||||
|
|
||||||
---
|
---
|
||||||
#let x = "x"
|
#let x = "x"
|
||||||
|
|
||||||
// Error: 1-3 expected callable or collection, found string
|
// Error: 1-3 expected function, found string
|
||||||
#x()
|
#x()
|
||||||
|
|
||||||
---
|
---
|
||||||
#let f(x) = x
|
#let f(x) = x
|
||||||
|
|
||||||
// Error: 1-6 expected callable or collection, found integer
|
// Error: 1-6 expected function, found integer
|
||||||
#f(1)(2)
|
#f(1)(2)
|
||||||
|
|
||||||
---
|
---
|
||||||
#let f(x) = x
|
#let f(x) = x
|
||||||
|
|
||||||
// Error: 1-6 expected callable or collection, found content
|
// Error: 1-6 expected function, found content
|
||||||
#f[1](2)
|
#f[1](2)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -12,17 +12,17 @@
|
|||||||
#dict
|
#dict
|
||||||
|
|
||||||
#test(dict.normal, 1)
|
#test(dict.normal, 1)
|
||||||
#test(dict("spacy key"), 2)
|
#test(dict.at("spacy key"), 2)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test lvalue and rvalue access.
|
// Test lvalue and rvalue access.
|
||||||
{
|
{
|
||||||
let dict = (a: 1, "b b": 1)
|
let dict = (a: 1, "b b": 1)
|
||||||
dict("b b") += 1
|
dict.at("b b") += 1
|
||||||
dict.state = (ok: true, err: false)
|
dict.state = (ok: true, err: false)
|
||||||
test(dict, (a: 1, "b b": 2, state: (ok: true, err: false)))
|
test(dict, (a: 1, "b b": 2, state: (ok: true, err: false)))
|
||||||
test(dict.state.ok, true)
|
test(dict.state.ok, true)
|
||||||
dict("state").ok = false
|
dict.at("state").ok = false
|
||||||
test(dict.state.ok, false)
|
test(dict.state.ok, false)
|
||||||
test(dict.state.err, false)
|
test(dict.state.err, false)
|
||||||
}
|
}
|
||||||
@ -31,18 +31,30 @@
|
|||||||
// Test rvalue missing key.
|
// Test rvalue missing key.
|
||||||
{
|
{
|
||||||
let dict = (a: 1, b: 2)
|
let dict = (a: 1, b: 2)
|
||||||
// Error: 11-20 dictionary does not contain key "c"
|
// Error: 11-23 dictionary does not contain key "c"
|
||||||
let x = dict("c")
|
let x = dict.at("c")
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Missing lvalue is automatically none-initialized.
|
// Missing lvalue is automatically none-initialized.
|
||||||
{
|
{
|
||||||
let dict = (:)
|
let dict = (:)
|
||||||
dict("b") += 1
|
dict.at("b") += 1
|
||||||
test(dict, (b: 1))
|
test(dict, (b: 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// 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))
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 24-32 pair has duplicate key
|
// Error: 24-32 pair has duplicate key
|
||||||
{(first: 1, second: 2, first: 3)}
|
{(first: 1, second: 2, first: 3)}
|
||||||
@ -65,7 +77,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-15 cannot mutate a temporary value
|
// Error: 3-15 cannot mutate a temporary value
|
||||||
{ (key: value).other = "some" }
|
{ (key: "val").other = "some" }
|
||||||
|
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
// Test the collection methods.
|
|
||||||
// Ref: false
|
|
||||||
|
|
||||||
---
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
---
|
|
||||||
// The the `first` and `last` methods.
|
|
||||||
#test(().first(), none)
|
|
||||||
#test(().last(), none)
|
|
||||||
#test((1,).first(), 1)
|
|
||||||
#test((2,).last(), 2)
|
|
||||||
#test((1, 2, 3).first(), 1)
|
|
||||||
#test((1, 2, 3).last(), 3)
|
|
||||||
|
|
||||||
---
|
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
---
|
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
---
|
|
||||||
// Error: 2:17-2:19 missing argument: index
|
|
||||||
#let numbers = ()
|
|
||||||
{ numbers.insert() }
|
|
||||||
|
|
||||||
---
|
|
||||||
// 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(range(10).slice(-5, count: 2), (5, 6))
|
|
||||||
#test((1, 2, 3).slice(2, -2), ())
|
|
||||||
#test((1, 2, 3).slice(-2, 2), (2,))
|
|
||||||
#test((1, 2, 3).slice(-3, 2), (1, 2))
|
|
||||||
#test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D")
|
|
||||||
|
|
||||||
---
|
|
||||||
// Error: 3-31 array index out of bounds (index: 12, len: 10)
|
|
||||||
{ range(10).slice(9, count: 3) }
|
|
||||||
|
|
||||||
---
|
|
||||||
// Error: 3-25 array index out of bounds (index: -4, len: 3)
|
|
||||||
{ (1, 2, 3).slice(0, -4) }
|
|
||||||
|
|
||||||
---
|
|
||||||
// Test the `position` method.
|
|
||||||
#test(("Hi", "❤️", "Love").position(s => s == "❤️"), 1)
|
|
||||||
#test(("Bye", "💘", "Apart").position(s => s == "❤️"), none)
|
|
||||||
#test(("A", "B", "CDEF", "G").position(v => v.len() > 2), 2)
|
|
||||||
|
|
||||||
---
|
|
||||||
// Test the `rev` method.
|
|
||||||
#test(range(3).rev(), (2, 1, 0))
|
|
||||||
|
|
||||||
---
|
|
||||||
// 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))
|
|
@ -9,7 +9,7 @@
|
|||||||
// Test mutating indexed value.
|
// Test mutating indexed value.
|
||||||
{
|
{
|
||||||
let matrix = (((1,), (2,)), ((3,), (4,)))
|
let matrix = (((1,), (2,)), ((3,), (4,)))
|
||||||
matrix(1)(0).push(5)
|
matrix.at(1).at(0).push(5)
|
||||||
test(matrix, (((1,), (2,)), ((3, 5), (4,))))
|
test(matrix, (((1,), (2,)), ((3, 5), (4,))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,20 +90,29 @@
|
|||||||
{
|
{
|
||||||
let x = 2
|
let x = 2
|
||||||
for _ in range(61) {
|
for _ in range(61) {
|
||||||
x *= 2
|
(x) *= 2
|
||||||
}
|
}
|
||||||
// Error: 4-18 cannot repeat this string 4611686018427387904 times
|
// Error: 4-18 cannot repeat this string 4611686018427387904 times
|
||||||
{x * "abcdefgh"}
|
{x * "abcdefgh"}
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-6 cannot mutate a temporary value
|
// Error: 4-5 unknown variable
|
||||||
{ (x) = "" }
|
{ (x) = "" }
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-8 cannot mutate a temporary value
|
// Error: 3-8 cannot mutate a temporary value
|
||||||
{ 1 + 2 += 3 }
|
{ 1 + 2 += 3 }
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 2:2-2:7 cannot apply 'not' to string
|
||||||
|
#let x = "Hey"
|
||||||
|
{not x = "a"}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 7-8 unknown variable
|
||||||
|
{ 1 + x += 3 }
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-4 unknown variable
|
// Error: 3-4 unknown variable
|
||||||
{ z = 1 }
|
{ z = 1 }
|
||||||
|
@ -12,8 +12,10 @@
|
|||||||
#test("a" == "a" and 2 < 3, true)
|
#test("a" == "a" and 2 < 3, true)
|
||||||
#test(not "b" == "b", false)
|
#test(not "b" == "b", false)
|
||||||
|
|
||||||
|
---
|
||||||
// Assignment binds stronger than boolean operations.
|
// Assignment binds stronger than boolean operations.
|
||||||
// Error: 2-7 cannot mutate a temporary value
|
// Error: 2:2-2:7 cannot mutate a temporary value
|
||||||
|
#let x = false
|
||||||
{not x = "a"}
|
{not x = "a"}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -40,7 +40,7 @@ Hello *{x}*
|
|||||||
// Test relative path resolving in layout phase.
|
// Test relative path resolving in layout phase.
|
||||||
#let choice = ("monkey.svg", "rhino.png", "tiger.jpg")
|
#let choice = ("monkey.svg", "rhino.png", "tiger.jpg")
|
||||||
#set enum(numbering: n => {
|
#set enum(numbering: n => {
|
||||||
let path = "../../res/" + choice(n - 1)
|
let path = "../../res/" + choice.at(n - 1)
|
||||||
move(dy: -0.15em, image(path, width: 1em, height: 1em))
|
move(dy: -0.15em, image(path, width: 1em, height: 1em))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,6 +1,35 @@
|
|||||||
// Test the string methods.
|
// Test the string methods.
|
||||||
// Ref: false
|
// Ref: false
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `len` method.
|
||||||
|
#test("Hello World!".len(), 12)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `first` and `last` methods.
|
||||||
|
#test("Hello".first(), "H")
|
||||||
|
#test("Hello".last(), "o")
|
||||||
|
#test("🏳️🌈A🏳️⚧️".first(), "🏳️🌈")
|
||||||
|
#test("🏳️🌈A🏳️⚧️".last(), "🏳️⚧️")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 3-13 string is empty
|
||||||
|
{ "".first() }
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 3-12 string is empty
|
||||||
|
{ "".last() }
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `at` method.
|
||||||
|
#test("Hello".at(1), "e")
|
||||||
|
#test("Hello".at(4), "o")
|
||||||
|
#test("Hey: 🏳️🌈 there!".at(5), "🏳️🌈")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 3-16 string index out of bounds (index: 5, len: 5)
|
||||||
|
{ "Hello".at(5) }
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `slice` method.
|
// Test the `slice` method.
|
||||||
#test("abc".slice(1, 2), "b")
|
#test("abc".slice(1, 2), "b")
|
||||||
@ -57,7 +86,7 @@
|
|||||||
let time = 0
|
let time = 0
|
||||||
for match in text.matches(regex("(\d+):(\d+)")) {
|
for match in text.matches(regex("(\d+):(\d+)")) {
|
||||||
let caps = match.captures
|
let caps = match.captures
|
||||||
time += 60 * int(caps(0)) + int(caps(1))
|
time += 60 * int(caps.at(0)) + int(caps.at(1))
|
||||||
}
|
}
|
||||||
str(int(time / 60)) + ":" + str(mod(time, 60))
|
str(int(time / 60)) + ":" + str(mod(time, 60))
|
||||||
}
|
}
|
@ -19,8 +19,8 @@
|
|||||||
// Ref: true
|
// Ref: true
|
||||||
#set page(width: auto)
|
#set page(width: auto)
|
||||||
#let data = csv("/res/zoo.csv")
|
#let data = csv("/res/zoo.csv")
|
||||||
#let cells = data(0).map(strong) + data.slice(1).flatten()
|
#let cells = data.at(0).map(strong) + data.slice(1).flatten()
|
||||||
#table(columns: data(0).len(), ..cells)
|
#table(columns: data.at(0).len(), ..cells)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 6-16 file not found (searched at typ/compute/nope.csv)
|
// Error: 6-16 file not found (searched at typ/compute/nope.csv)
|
||||||
@ -34,8 +34,8 @@
|
|||||||
// Test reading JSON data.
|
// Test reading JSON data.
|
||||||
#let data = json("/res/zoo.json")
|
#let data = json("/res/zoo.json")
|
||||||
#test(data.len(), 3)
|
#test(data.len(), 3)
|
||||||
#test(data(0).name, "Debby")
|
#test(data.at(0).name, "Debby")
|
||||||
#test(data(2).weight, 150)
|
#test(data.at(2).weight, 150)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 7-22 failed to parse json file: syntax error in line 3
|
// Error: 7-22 failed to parse json file: syntax error in line 3
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
#for section in sections [
|
#for section in sections [
|
||||||
#section(0) #repeat[.] #section(1) \
|
{section.at(0)} #repeat[.] {section.at(1)} \
|
||||||
]
|
]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
Loading…
x
Reference in New Issue
Block a user